Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
sed -i 's/"__SENDGRID_API_KEY",/"${{ secrets.SENDGRID_API_KEY }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
sed -i 's/"__SENDGRID_WEBHOOK_SIGNATURE",/"${{ secrets.SENDGRID_WEBHOOK_SIGNATURE }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
sed -i 's/"__OPEN_AI_SECRET",/"${{ secrets.OPEN_AI_SECRET }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
sed -i 's/"__OPENAI_API_KEY",/"${{ secrets.OPENAI_API_KEY }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
cd src
docker login -u ${{ env.DO_TOKEN }} -p ${{ env.DO_TOKEN }} registry.digitalocean.com/techinterview
docker build -f Dockerfile -t ${{ secrets.CR }}/backend:${{ github.sha }} .
Expand Down
3 changes: 2 additions & 1 deletion src/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ IdentityServer_Audience=''
Telegram__SalariesBotToken=''
Telegram__GithubProfileBotToken=''
OpenAiApi__Secret=''
Telegram__GithubPATForLocalDevelopment=''
Telegram__GithubPATForLocalDevelopment=''
OpenAI__ApiKey=''
10 changes: 10 additions & 0 deletions src/Domain/Entities/Companies/Company.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ public List<CompanyReview> GetRelevantReviews()
.ToList();
}

public bool HasRelevantReviews()
{
if (Reviews == null)
{
throw new InvalidOperationException("Reviews are not initialized.");
}

return Reviews.Any(x => x.IsRelevant());
}

public bool IsUserAllowedToLeaveReview(
long userId)
{
Expand Down
84 changes: 84 additions & 0 deletions src/Infrastructure/Services/OpenAi/Custom/CustomOpenAiService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Infrastructure.Jwt;
using Infrastructure.Services.OpenAi.Custom.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Infrastructure.Services.OpenAi.Custom;

public class CustomOpenAiService : ICustomOpenAiService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly ILogger<CustomOpenAiService> _logger;

public CustomOpenAiService(
IHttpClientFactory httpClientFactory,
IConfiguration configuration,
ILogger<CustomOpenAiService> logger)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
_logger = logger;
}

public string GetBearer()
{
var secret = _configuration["OpenAiApiSecret"];
return new TechinterviewJwtTokenGenerator(secret).ToString();
}
Comment on lines +27 to +31
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

Cache or optimize JWT token generation.

Creating a new TechinterviewJwtTokenGenerator instance on every call is inefficient. Consider caching the generator or the token with appropriate expiration.

+    private TechinterviewJwtTokenGenerator _jwtGenerator;
+
     public string GetBearer()
     {
         var secret = _configuration["OpenAiApiSecret"];
-        return new TechinterviewJwtTokenGenerator(secret).ToString();
+        _jwtGenerator ??= new TechinterviewJwtTokenGenerator(secret);
+        return _jwtGenerator.ToString();
     }
📝 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 string GetBearer()
{
var secret = _configuration["OpenAiApiSecret"];
return new TechinterviewJwtTokenGenerator(secret).ToString();
}
private TechinterviewJwtTokenGenerator _jwtGenerator;
public string GetBearer()
{
var secret = _configuration["OpenAiApiSecret"];
_jwtGenerator ??= new TechinterviewJwtTokenGenerator(secret);
return _jwtGenerator.ToString();
}
🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Custom/CustomOpenAiService.cs around lines
27 to 31, the GetBearer method creates a new TechinterviewJwtTokenGenerator
instance on every call, which is inefficient. Refactor the code to cache either
the TechinterviewJwtTokenGenerator instance or the generated JWT token,
implementing appropriate expiration logic to refresh the token when needed,
thereby avoiding unnecessary object creation and improving performance.


public async Task<string> GetAnalysisAsync(
OpenAiBodyReport report,
CancellationToken cancellationToken = default)
{
var apiUrl = _configuration["OpenAiApiUrl"];
if (string.IsNullOrEmpty(apiUrl))
{
throw new InvalidOperationException("OpenAI API url is not set");
}

var responseContent = string.Empty;
try
{
using var client = _httpClientFactory.CreateClient();

client.BaseAddress = new Uri(apiUrl);
var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", GetBearer());
request.Content = new StringContent(
JsonSerializer.Serialize(report),
Encoding.UTF8,
"application/json");
Comment on lines +46 to +54
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

Fix HTTP client usage pattern.

Setting BaseAddress after creating the client and then using the full URL in the request is redundant and potentially incorrect.

             using var client = _httpClientFactory.CreateClient();

-            client.BaseAddress = new Uri(apiUrl);
-            var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
+            var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
             request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", GetBearer());
📝 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
using var client = _httpClientFactory.CreateClient();
client.BaseAddress = new Uri(apiUrl);
var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", GetBearer());
request.Content = new StringContent(
JsonSerializer.Serialize(report),
Encoding.UTF8,
"application/json");
using var client = _httpClientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", GetBearer());
request.Content = new StringContent(
JsonSerializer.Serialize(report),
Encoding.UTF8,
"application/json");
🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Custom/CustomOpenAiService.cs around lines
46 to 54, the code sets the HttpClient's BaseAddress but then uses the full URL
in the HttpRequestMessage, which is redundant and can cause errors. To fix this,
either remove setting BaseAddress and keep using the full URL in the request, or
set BaseAddress and use a relative URI in the HttpRequestMessage constructor.
Adjust the code to follow one consistent pattern for HttpClient usage.


var response = await client.SendAsync(request, cancellationToken);

responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
if (response.IsSuccessStatusCode)
{
return responseContent;
}

_logger.LogError(
"Failed request to OpenAI {Url}. Status {Status}, Response {Response}",
apiUrl,
response.StatusCode,
responseContent);

return string.Empty;
}
Comment on lines +58 to +71
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

Consider using a Result pattern instead of returning empty string on failure.

Returning an empty string on failure makes it difficult for callers to distinguish between an empty successful response and a failure. Consider using a Result pattern or throwing exceptions.

🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Custom/CustomOpenAiService.cs around lines
58 to 71, the method currently returns an empty string on failure, which makes
it hard to differentiate between a failed request and a successful empty
response. Refactor the method to return a Result type that encapsulates success
or failure states along with the response content or error information, or
alternatively throw exceptions on failure to clearly signal errors to the
caller.

catch (Exception e)
{
_logger.LogError(
e,
"Error while getting OpenAI response from {Url}. Message {Message}. Response {Response}",
apiUrl,
e.Message,
responseContent);

return string.Empty;
}
}
}
12 changes: 12 additions & 0 deletions src/Infrastructure/Services/OpenAi/Custom/ICustomOpenAiService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Infrastructure.Services.OpenAi.Custom.Models;

namespace Infrastructure.Services.OpenAi.Custom;

public interface ICustomOpenAiService
{
string GetBearer();

Task<string> GetAnalysisAsync(
OpenAiBodyReport report,
CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Domain.Entities.Salaries;
using Infrastructure.Salaries;

namespace Infrastructure.Services.OpenAi.Models;
namespace Infrastructure.Services.OpenAi.Custom.Models;

public record OpenAiBodyReport
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Domain.Entities.Salaries;

namespace Infrastructure.Services.OpenAi.Models;
namespace Infrastructure.Services.OpenAi.Custom.Models;

public record OpenAiBodyReportMetadata
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Domain.Entities.Salaries;
using Domain.Entities.StatData;

namespace Infrastructure.Services.OpenAi.Models;
namespace Infrastructure.Services.OpenAi.Custom.Models;

public record OpenAiBodyReportRole
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Domain.Entities.StatData;
using Domain.Extensions;

namespace Infrastructure.Services.OpenAi.Models;
namespace Infrastructure.Services.OpenAi.Custom.Models;

public record OpenAiBodyReportRoleHistoricalDataItem
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Domain.Entities.StatData;
using Domain.Extensions;

namespace Infrastructure.Services.OpenAi.Models;
namespace Infrastructure.Services.OpenAi.Custom.Models;

public record OpenAiBodyReportRoleSalaryData
{
Expand Down
13 changes: 9 additions & 4 deletions src/Infrastructure/Services/OpenAi/IOpenAiService.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
using Infrastructure.Services.OpenAi.Models;
using Domain.Entities.Companies;
using Infrastructure.Services.OpenAi.Models;

namespace Infrastructure.Services.OpenAi;

public interface IOpenAiService
{
string GetBearer();
Task<OpenAiChatResult> AnalyzeCompanyAsync(
Company company,
string correlationId = null,
CancellationToken cancellationToken = default);

Task<string> GetAnalysisAsync(
OpenAiBodyReport report,
Task<OpenAiChatResult> AnalyzeChatAsync(
string input,
string correlationId = null,
CancellationToken cancellationToken = default);
}
Comment on lines +1 to 17
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

Add missing using statement for CancellationToken.

The interface is missing using System.Threading; for the CancellationToken type used in method signatures.

 using Domain.Entities.Companies;
 using Infrastructure.Services.OpenAi.Models;
+using System.Threading;
+using System.Threading.Tasks;

 namespace Infrastructure.Services.OpenAi;
📝 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
using Domain.Entities.Companies;
using Infrastructure.Services.OpenAi.Models;
namespace Infrastructure.Services.OpenAi;
public interface IOpenAiService
{
string GetBearer();
Task<OpenAiChatResult> AnalyzeCompanyAsync(
Company company,
string correlationId = null,
CancellationToken cancellationToken = default);
Task<string> GetAnalysisAsync(
OpenAiBodyReport report,
Task<OpenAiChatResult> AnalyzeChatAsync(
string input,
string correlationId = null,
CancellationToken cancellationToken = default);
}
using Domain.Entities.Companies;
using Infrastructure.Services.OpenAi.Models;
using System.Threading;
using System.Threading.Tasks;
namespace Infrastructure.Services.OpenAi;
public interface IOpenAiService
{
Task<OpenAiChatResult> AnalyzeCompanyAsync(
Company company,
string correlationId = null,
CancellationToken cancellationToken = default);
Task<OpenAiChatResult> AnalyzeChatAsync(
string input,
string correlationId = null,
CancellationToken cancellationToken = default);
}
🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/IOpenAiService.cs at lines 1 to 17, add
the missing using directive for System.Threading at the top of the file to
resolve the undefined CancellationToken type used in the method signatures.

24 changes: 24 additions & 0 deletions src/Infrastructure/Services/OpenAi/Models/ChatMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Text.Json.Serialization;

namespace Infrastructure.Services.OpenAi.Models;

public record ChatMessage
{
public ChatMessage(
string role,
string content)
{
Role = role;
Content = content;
}

public ChatMessage()
{
}
Comment on lines +15 to +17
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

Remove unnecessary parameterless constructor.

The parameterless constructor is redundant since records automatically provide one when properties use init accessors.

-    public ChatMessage()
-    {
-    }
📝 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 ChatMessage()
{
}
🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Models/ChatMessage.cs at lines 15 to 17,
remove the explicit parameterless constructor since the record type
automatically provides one when properties use init accessors, making this
constructor redundant.


[JsonPropertyName("role")]
public string Role { get; set; } // "user", "assistant", or "system"

[JsonPropertyName("content")]
public string Content { get; set; }
}
Comment on lines +5 to +24
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

Improve immutability, validation, and documentation.

The record has several design issues:

  1. Properties should be immutable to prevent external modification
  2. Role property should validate against expected values mentioned in the comment
  3. Missing null reference handling
  4. Missing XML documentation for this public API model

Apply this diff to improve the design:

+/// <summary>
+/// Represents a chat message with role and content for OpenAI API communication.
+/// </summary>
 public record ChatMessage
 {
+    private static readonly HashSet<string> ValidRoles = new() { "user", "assistant", "system" };
+
     public ChatMessage(
         string role,
         string content)
     {
+        if (string.IsNullOrWhiteSpace(role))
+            throw new ArgumentException("Role cannot be null or empty.", nameof(role));
+        if (!ValidRoles.Contains(role))
+            throw new ArgumentException($"Invalid role '{role}'. Expected: {string.Join(", ", ValidRoles)}", nameof(role));
+        if (string.IsNullOrWhiteSpace(content))
+            throw new ArgumentException("Content cannot be null or empty.", nameof(content));
+            
         Role = role;
         Content = content;
     }

-    public ChatMessage()
-    {
-    }

     [JsonPropertyName("role")]
-    public string Role { get; set; } // "user", "assistant", or "system"
+    public string Role { get; init; } = string.Empty; // "user", "assistant", or "system"

     [JsonPropertyName("content")]
-    public string Content { get; set; }
+    public string Content { get; init; } = string.Empty;
 }
📝 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 ChatMessage
{
public ChatMessage(
string role,
string content)
{
Role = role;
Content = content;
}
public ChatMessage()
{
}
[JsonPropertyName("role")]
public string Role { get; set; } // "user", "assistant", or "system"
[JsonPropertyName("content")]
public string Content { get; set; }
}
/// <summary>
/// Represents a chat message with role and content for OpenAI API communication.
/// </summary>
public record ChatMessage
{
private static readonly HashSet<string> ValidRoles = new() { "user", "assistant", "system" };
public ChatMessage(
string role,
string content)
{
if (string.IsNullOrWhiteSpace(role))
throw new ArgumentException("Role cannot be null or empty.", nameof(role));
if (!ValidRoles.Contains(role))
throw new ArgumentException($"Invalid role '{role}'. Expected: {string.Join(", ", ValidRoles)}", nameof(role));
if (string.IsNullOrWhiteSpace(content))
throw new ArgumentException("Content cannot be null or empty.", nameof(content));
Role = role;
Content = content;
}
[JsonPropertyName("role")]
public string Role { get; init; } = string.Empty; // "user", "assistant", or "system"
[JsonPropertyName("content")]
public string Content { get; init; } = string.Empty;
}
🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Models/ChatMessage.cs lines 5 to 24, make
the properties immutable by using init-only setters or readonly fields to
prevent external modification. Add validation in the constructor to ensure the
Role value is one of the expected strings ("user", "assistant", or "system") and
throw an exception if invalid. Include null checks for both Role and Content
parameters to prevent null reference issues. Finally, add XML documentation
comments to the class and its members to clearly describe their purpose and
usage.

15 changes: 15 additions & 0 deletions src/Infrastructure/Services/OpenAi/Models/ChatRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace Infrastructure.Services.OpenAi.Models;

public record ChatRequest
{
[JsonPropertyName("model")]
public string Model { get; set; }
Comment on lines +7 to +8
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

Use init accessors for record immutability.

For record types, properties should use init accessors instead of set to maintain immutability and leverage record semantics properly.

-    public string Model { get; set; }
+    public string Model { get; init; }

-    public List<ChatMessage> Messages { get; set; }
+    public List<ChatMessage> Messages { get; init; }

-    public double Temperature { get; set; } = 0.7;
+    public double Temperature { get; init; } = 0.7;

Additionally, consider adding null-ability annotations:

-    public string Model { get; init; }
-    public List<ChatMessage> Messages { get; init; }
+    public string Model { get; init; } = string.Empty;
+    public List<ChatMessage> Messages { get; init; } = new();

Also applies to: 10-11, 13-14

🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Models/ChatRequest.cs around lines 7-8 and
also lines 10-11 and 13-14, replace the property setters from 'set' to 'init' to
enforce immutability in this record type. Additionally, add nullability
annotations to these properties to explicitly indicate whether they can be null
or not, improving code safety and clarity.


[JsonPropertyName("messages")]
public List<ChatMessage> Messages { get; set; }

[JsonPropertyName("temperature")]
public double Temperature { get; set; } = 0.7;
}
9 changes: 9 additions & 0 deletions src/Infrastructure/Services/OpenAi/Models/ChatResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace Infrastructure.Services.OpenAi.Models;

public record ChatResponse
{
[JsonPropertyName("choices")]
public List<Choice> Choices { get; set; }
}
Comment on lines +5 to +9
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

Improve immutability and null safety for the data model.

The record has several design concerns:

  1. The Choices property should be immutable to prevent external modification
  2. Missing null reference handling could cause runtime exceptions
  3. Missing XML documentation for this public API model

Apply this diff to improve the design:

+/// <summary>
+/// Represents a chat response from OpenAI API containing multiple choices.
+/// </summary>
 public record ChatResponse
 {
     [JsonPropertyName("choices")]
-    public List<Choice> Choices { get; set; }
+    public IReadOnlyList<Choice> Choices { get; init; } = Array.Empty<Choice>();
 }
📝 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 ChatResponse
{
[JsonPropertyName("choices")]
public List<Choice> Choices { get; set; }
}
/// <summary>
/// Represents a chat response from OpenAI API containing multiple choices.
/// </summary>
public record ChatResponse
{
[JsonPropertyName("choices")]
public IReadOnlyList<Choice> Choices { get; init; } = Array.Empty<Choice>();
}
🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Models/ChatResponse.cs around lines 5 to
9, the Choices property should be made immutable by changing it to an init-only
property or a read-only collection to prevent external modification. Initialize
the Choices property to an empty list or use nullable annotations and add null
checks to avoid runtime null reference exceptions. Additionally, add XML
documentation comments to the ChatResponse record and its properties to improve
code clarity and maintainability.

9 changes: 9 additions & 0 deletions src/Infrastructure/Services/OpenAi/Models/Choice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;

namespace Infrastructure.Services.OpenAi.Models;

public record Choice
{
[JsonPropertyName("message")]
public ChatMessage Message { get; set; }
}
84 changes: 84 additions & 0 deletions src/Infrastructure/Services/OpenAi/Models/CompanyAnalyzeRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using Domain.Entities.Companies;

namespace Infrastructure.Services.OpenAi.Models;

internal record CompanyAnalyzeRequest
{
public CompanyAnalyzeRequest(
Company company)
{
Name = company.Name;
Rating = company.Rating;
RatingHistoryRecords = company.RatingHistory?
.Select(history => new CompanyRatingHistoryItem(history))
.ToList();

Reviews = company.Reviews
.Select(x => new CompanyReviewData(x))
.ToList();
Comment on lines +12 to +18
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 consistent null checking for defensive programming.

The code uses null-conditional operator for RatingHistory but not for Reviews. Consider adding null checks or using consistent patterns to prevent potential NullReferenceException.

         RatingHistoryRecords = company.RatingHistory?
             .Select(history => new CompanyRatingHistoryItem(history))
             .ToList();

-        Reviews = company.Reviews
+        Reviews = company.Reviews?
             .Select(x => new CompanyReviewData(x))
-            .ToList();
+            .ToList() ?? new List<CompanyReviewData>();

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

🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Models/CompanyAnalyzeRequest.cs between
lines 12 and 18, the code uses a null-conditional operator for RatingHistory but
lacks null checking for Reviews, which can cause NullReferenceException. Add a
null-conditional operator or a null check before selecting and converting
Reviews to a list, ensuring consistent defensive programming and preventing
runtime errors.

}

public double Rating { get; }

public string Name { get; }

public List<CompanyRatingHistoryItem> RatingHistoryRecords { get; }

public List<CompanyReviewData> Reviews { get; }
}

internal record CompanyReviewData
{
public CompanyReviewData(
CompanyReview review)
{
Pros = review.Pros;
Cons = review.Cons;
CultureAndValues = review.CultureAndValues;
CodeQuality = review.CodeQuality;
WorkLifeBalance = review.WorkLifeBalance;
Management = review.Management;
CompensationAndBenefits = review.CompensationAndBenefits;
CareerOpportunities = review.CareerOpportunities;
TotalRating = review.TotalRating;
LikesCount = review.LikesCount;
DislikesCount = review.DislikesCount;
TotalRating = review.TotalRating;
}
Comment on lines +46 to +47
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

Remove duplicate TotalRating assignment.

TotalRating is assigned twice in the constructor.

         TotalRating = review.TotalRating;
         LikesCount = review.LikesCount;
         DislikesCount = review.DislikesCount;
-        TotalRating = review.TotalRating;
📝 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
TotalRating = review.TotalRating;
}
// in CompanyAnalyzeRequest constructor, replace these lines:
TotalRating = review.TotalRating;
LikesCount = review.LikesCount;
DislikesCount = review.DislikesCount;
// (remove the duplicate `TotalRating = review.TotalRating;` here)
🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Models/CompanyAnalyzeRequest.cs at lines
46 to 47, there is a duplicate assignment of the TotalRating property in the
constructor. Remove the redundant assignment so that TotalRating is only
assigned once to avoid unnecessary repetition.


public string Pros { get; }

public string Cons { get; }

public int CultureAndValues { get; }

public int? CodeQuality { get; }

public int WorkLifeBalance { get; }

public int Management { get; }

public int CompensationAndBenefits { get; }

public int CareerOpportunities { get; }

public double TotalRating { get; }

public int LikesCount { get; }

public int DislikesCount { get; }
}

internal record CompanyRatingHistoryItem
{
public CompanyRatingHistoryItem(
CompanyRatingHistory history)
{
CreatedAt = history.CreatedAt;
Rating = history.Rating;
}

public DateTime CreatedAt { get; }

public double Rating { get; }
}
26 changes: 26 additions & 0 deletions src/Infrastructure/Services/OpenAi/Models/OpenAiChatResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Infrastructure.Services.OpenAi.Models;

public record OpenAiChatResult
{
public static OpenAiChatResult Success(List<Choice> choices)
{
return new OpenAiChatResult(true, choices);
}

public static OpenAiChatResult Failure()
{
return new OpenAiChatResult(false, new List<Choice>());
}

private OpenAiChatResult(
bool isSuccess,
List<Choice> choices)
{
IsSuccess = isSuccess;
Choices = choices;
}

public bool IsSuccess { get; }

public List<Choice> Choices { get; }
}
Comment on lines +1 to +26
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

Add missing using statement and verify Choice dependency.

The file is missing the using System.Collections.Generic; statement for List<Choice>. Also ensure that the Choice type is properly defined and accessible in the same namespace.

+using System.Collections.Generic;
+
 namespace Infrastructure.Services.OpenAi.Models;

Let me verify the Choice type exists:


🏁 Script executed:

#!/bin/bash
# Description: Check if Choice type is defined in the same namespace
# Expected: Find Choice type definition in Models namespace

rg -A 5 "class Choice|record Choice" --include="*.cs"

Length of output: 497


🏁 Script executed:

#!/bin/bash
# Description: Check for Choice type definition in C# files
rg -R -n -C5 -E 'record\s+Choice|class\s+Choice' --glob '*.cs'

Length of output: 469


🏁 Script executed:

#!/bin/bash
# Search for Choice type definitions across C# files
rg -n -C5 -E 'record\s+Choice|class\s+Choice' --glob '*.cs' .

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Search for Choice type definitions in C# files
find . -type f -name '*.cs' -exec grep -nE 'class[[:space:]]+Choice|record[[:space:]]+Choice' {} +

Length of output: 179


Add missing System.Collections.Generic using

The List<Choice> usage requires importing the generic collections namespace. The Choice record is already defined in src/Infrastructure/Services/OpenAi/Models/Choice.cs under the same namespace, so no further action is needed for that.

Apply this diff:

+using System.Collections.Generic;

 namespace Infrastructure.Services.OpenAi.Models;
📝 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
namespace Infrastructure.Services.OpenAi.Models;
public record OpenAiChatResult
{
public static OpenAiChatResult Success(List<Choice> choices)
{
return new OpenAiChatResult(true, choices);
}
public static OpenAiChatResult Failure()
{
return new OpenAiChatResult(false, new List<Choice>());
}
private OpenAiChatResult(
bool isSuccess,
List<Choice> choices)
{
IsSuccess = isSuccess;
Choices = choices;
}
public bool IsSuccess { get; }
public List<Choice> Choices { get; }
}
using System.Collections.Generic;
namespace Infrastructure.Services.OpenAi.Models;
public record OpenAiChatResult
{
public static OpenAiChatResult Success(List<Choice> choices)
{
return new OpenAiChatResult(true, choices);
}
public static OpenAiChatResult Failure()
{
return new OpenAiChatResult(false, new List<Choice>());
}
private OpenAiChatResult(
bool isSuccess,
List<Choice> choices)
{
IsSuccess = isSuccess;
Choices = choices;
}
public bool IsSuccess { get; }
public List<Choice> Choices { get; }
}
🤖 Prompt for AI Agents
In src/Infrastructure/Services/OpenAi/Models/OpenAiChatResult.cs lines 1 to 26,
the code uses List<Choice> but is missing the necessary using directive for
System.Collections.Generic. Add 'using System.Collections.Generic;' at the top
of the file to resolve this.

Loading