Skip to content

Reimplelemtation - #157

Merged
maximgorbatyuk merged 5 commits into
mainfrom
historical-data
Dec 26, 2025
Merged

maximgorbatyuk merged 5 commits into
mainfrom
historical-data

Conversation

@maximgorbatyuk

@maximgorbatyuk maximgorbatyuk commented Dec 26, 2025 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Historical salary data tracking with daily snapshots and templates.
    • Admin API to create, update, list and delete salary-history templates.
    • Charts updated to use recorded historical data for improved trend reporting.
    • Scheduled background job to collect and persist daily salary statistics.
  • Tests

    • Added comprehensive tests for historical data job and template CRUD flows.
  • Chores

    • Updated multiple package versions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 26, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces historical salaries storage (templates + daily records), DB schema, CRUD handlers and controller for templates, a background job to populate daily records, refactors salaries query params from interface to concrete record, removes an old chart component, updates tests and dependencies, and adds EF Core migration.

Changes

Cohort / File(s) Summary
Ignore & Project deps
/.gitignore, src/Domain/Domain.csproj, src/Infrastructure/Infrastructure.csproj, src/TestUtils/TestUtils.csproj, src/Web.Api/Web.Api.csproj
Added src/dotnet-analysis.json to .gitignore; bumped several NuGet package versions; removed MaximGorbatyuk.DatabaseSqlEndpoints.
Domain — Historical Entities
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs, src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs
Added domain entities for templates and historical records with IDs, timestamps, template/profession data and constructors (ORM-friendly).
Domain — Query Params
src/Domain/ValueObjects/ISalariesChartQueryParams.cs, src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs
Removed HasAnyFilter/GetKeyPostfix from interface; added concrete SalariesChartQueryParamsBase record with HasAnyFilter and GetKeyPostfix implementations (domain side).
Infrastructure — EF Config & Context
src/Infrastructure/Database/Config/HistoricalRecords/*, src/Infrastructure/Database/DatabaseContext.cs, src/Infrastructure/Migrations/*, src/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
Added EF entity configurations (JSON conversions, indexes, cascade FK), DbSet properties, migration to create templates and records tables, snapshot ProductVersion update.
Infrastructure — Salaries plumbing
src/Infrastructure/Salaries/SalariesChartPageLink.cs, src/Infrastructure/Salaries/SalariesForChartQuery.cs, src/Infrastructure/Services/Telegram/UserCommands/TelegramBotUserCommandParameters.cs
Replaced ISalariesChartQueryParams usages with SalariesChartQueryParamsBase; adjusted Telegram bot parameter type and overridden properties.
Web API — Historical chart (removed)
src/Web.Api/Features/Historical/GetSalariesHistoricalChart/Charts/SalariesCountWeekByWeekChart.cs, src/Web.Api.Tests/.../SalariesCountWeekByWeekChartTests.cs
Removed weekly aggregation chart implementation and its test suite.
Web API — Historical chart (new flow & DTOs)
src/Web.Api/Features/Historical/GetSalariesHistoricalChart/*
Reworked GetSalariesHistoricalChart to query SalariesHistoricalDataRecords grouped by template; replaced chart DTOs with HistoricalDataByTemplate/HistoricalDataPoint.
Web API — Background Job
src/Web.Api/Features/BackgroundJobs/Salaries/SalariesHistoricalDataJob.cs, src/Web.Api/Setup/ScheduleConfig.cs, src/Web.Api/Startup.cs
Added SalariesHistoricalDataJob (loads templates, queries salaries, creates daily records), registered/scheduled job; removed SQL endpoints setup from Startup.
Web API — Templates CRUD
src/Web.Api/Features/SalariesHistoricalDataTemplates/** (CreateTemplate, DeleteTemplate, GetTemplates, UpdateTemplate, Shared, Controller)
Added DTOs/commands/handlers/controller for Create, Get (paged), Update, Delete templates; validation, profession existence checks, mapping DTOs and pagination.
Tests & Test utils
src/TestUtils/Fakes/SalariesHistoricalDataRecordTemplateFake.cs, src/Web.Api.Tests/Features/... (new test files)
Added fake template helper and new test suites covering job behavior and template CRUD handlers (create/delete/get/update), removed an old chart test file.
Other small changes
src/Web.Api/Features/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs, src/Web.Api/Features/Salaries/*
Reworked cache key usage to rely on CreateDatabaseQueryParams(); updated handlers to call CreateDatabaseQueryParams() instead of using removed postfix methods.

Sequence Diagram(s)

sequenceDiagram
    participant Job as SalariesHistoricalDataJob
    participant DB as DatabaseContext
    participant Query as SalariesForChartQuery
    participant Record as SalariesHistoricalDataRecord

    Job->>DB: Load all SalariesHistoricalDataRecordTemplates
    DB-->>Job: List<Template>

    loop per template
        Job->>DB: Check for existing record for today (TemplateId + Date)
        DB-->>Job: existing? / null

        alt no existing record
            Job->>Query: Build SalariesForChartQuery(for template.professions)
            Query->>DB: Execute counts & load salaries (filter: has grade)
            DB-->>Query: salary count + salary items

            alt salaries found
                Query-->>Job: Aggregated SalariesStatDataCacheItemSalaryData
                Job->>DB: AddAsync(new SalariesHistoricalDataRecord)
                DB->>DB: SaveChangesAsync()
                DB-->>Job: saved
            else no salaries
                Job->>Job: Log "No salaries found"
            end
        else record exists
            Job->>Job: Log "record exists for today"
        end
    end
Loading
sequenceDiagram
    participant Client as API Client
    participant Controller as SalariesHistoricalDataTemplatesController
    participant Mediator as ServiceProvider/HandlerInvoker
    participant Handler as Template Handler
    participant DB as DatabaseContext

    Client->>Controller: POST/PUT/GET/DELETE request
    Controller->>Mediator: Handle command/query
    Mediator->>Handler: Invoke corresponding handler

    alt Create/Update/Delete
        Handler->>DB: Validate professions & fetch entities
        DB-->>Handler: entities/list
        alt validation fail
            Handler-->>Controller: throw BadRequest/NotFound
            Controller-->>Client: error response
        else ok
            Handler->>DB: Add/Update/Delete
            DB->>DB: SaveChangesAsync()
            Handler-->>Controller: DTO / Nothing
            Controller-->>Client: 200/201/204
        end
    else Get
        Handler->>DB: Query templates ordered by CreatedAt DESC
        DB-->>Handler: List<Template>
        Handler-->>Controller: Pageable<DTO>
        Controller-->>Client: 200 with paginated result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Adjusted models #152 – Adds/adjusts salary stat data types and aggregation logic used by the new historical-record creation flow.
  • Added jwt generator #101 – Introduces domain stat-data types (e.g., SalaryBaseData) referenced by the job and record payloads.
  • Added Github profile bot #119 – Overlaps with Telegram bot related refactors and provider/hosted-service changes used by Telegram integrations in this PR.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.27% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Reimplelemtation' appears to be a misspelling of 'Reimplement' and is vague. While it suggests re-implementation of some feature, it does not clearly convey what is being re-implemented or its scope relative to the substantial changeset. Replace with a more specific, correctly-spelled title that describes the main change, such as 'Implement salaries historical data recording and templates management' or 'Add historical salaries chart data persistence layer'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch historical-data

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 563e7be and 45aae86.

📒 Files selected for processing (2)
  • src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs
  • src/Web.Api/Web.Api.csproj
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Web.Api/Web.Api.csproj
  • src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (20)
src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateBodyRequest.cs (1)

5-6: Consider whether a separate type is needed.

This empty record only provides type distinction from UpdateSalariesHistoricalDataRecordTemplateBodyRequest. If Create and Update operations accept identical request bodies, consider using the shared base type directly to reduce unnecessary type proliferation.

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

37-48: Consider extracting duplicated CreateDatabaseQueryParams logic.

The CreateDatabaseQueryParams method appears duplicated in both SalariesChartQueryParamsBase and GetSalariesPaginatedQuery (based on relevant code snippets). This violates the DRY principle.

Consider extracting this logic into a shared extension method or ensuring the base implementation in SalariesChartQueryParamsBase is reused by derived types.

🔎 Example approach

If GetSalariesPaginatedQuery inherits from or uses SalariesChartQueryParamsBase, ensure it calls the base implementation:

public new Domain.ValueObjects.SalariesChartQueryParamsBase CreateDatabaseQueryParams()
{
    return base.CreateDatabaseQueryParams();
}

Or eliminate the override if the base implementation suffices.

src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (1)

37-43: Consider using nullable reference type annotations for list properties.

The list properties can be null when using the parameterless constructor, but this isn't reflected in the type declarations. Adding ? annotations would clarify nullability semantics. As per coding guidelines, proper use of nullable reference types improves null safety.

🔎 Proposed change
-public virtual List<long> SelectedProfessionIds { get; init; }
+public virtual List<long>? SelectedProfessionIds { get; init; }

-public virtual List<long> Skills { get; init; }
+public virtual List<long>? Skills { get; init; }

-public virtual List<KazakhstanCity> Cities { get; init; }
+public virtual List<KazakhstanCity>? Cities { get; init; }

-public virtual List<SalarySourceType> SalarySourceTypes { get; init; }
+public virtual List<SalarySourceType>? SalarySourceTypes { get; init; }
src/Web.Api/Features/Salaries/GetSalariesChart/GetSalariesChartHandler.cs (1)

56-67: Consider using DateTimeOffset.UtcNow for consistency.

Line 56 uses DateTimeOffset.Now (local server time), while other parts of the codebase and domain entities use DateTimeOffset.UtcNow. This could lead to inconsistent date handling across different server environments.

🔎 Proposed fix
-var endDateForStats = DateTimeOffset.Now;
+var endDateForStats = DateTimeOffset.UtcNow;
src/Web.Api/Features/BackgroundJobs/Salaries/SalariesHistoricalDataJob.cs (2)

50-67: N+1 query pattern: Consider batch-fetching existing records.

Each iteration queries SalariesHistoricalDataRecords separately for each template. For many templates, this results in N+1 database queries.

🔎 Proposed optimization

Pre-fetch all today's records in a single query before the loop:

 var now = DateTime.UtcNow;
 var today = new DateTimeOffset(now.Date, TimeSpan.Zero);

+var templateIds = templatesToBeProcessed.Select(t => t.Id).ToList();
+var existingRecordsToday = await _context.SalariesHistoricalDataRecords
+    .AsNoTracking()
+    .Where(x => templateIds.Contains(x.TemplateId) && x.Date == today)
+    .Select(x => x.TemplateId)
+    .ToHashSetAsync(cancellationToken);

 var createdRecordsCount = 0;
 foreach (var template in templatesToBeProcessed)
 {
-    var existingRecordForToday = await _context.SalariesHistoricalDataRecords
-        .AsNoTracking()
-        .Where(x => x.TemplateId == template.Id && x.Date == today)
-        .FirstOrDefaultAsync(cancellationToken);
-
-    if (existingRecordForToday != null)
+    if (existingRecordsToday.Contains(template.Id))
     {
         Logger.LogInformation(
-            "Record {RecordId} for template {TemplateId} does exist for today {Today}. CorrelationId: {CorrelationId}",
-            existingRecordForToday.Id,
+            "Record for template {TemplateId} already exists for today {Today}. CorrelationId: {CorrelationId}",
             template.Id,
-            existingRecordForToday.Date,
+            today,
             correlationId);

         continue;
     }

69-109: Consider adding try-catch for individual template processing.

If one template fails during processing (e.g., database error in salary query), the entire job stops. Wrapping template processing in try-catch would allow other templates to be processed.

🔎 Proposed enhancement
 foreach (var template in templatesToBeProcessed)
 {
+    try
+    {
         // ... existing template processing logic ...
+    }
+    catch (Exception ex)
+    {
+        Logger.LogError(
+            ex,
+            "Failed to process template {TemplateId}. CorrelationId: {CorrelationId}",
+            template.Id,
+            correlationId);
+    }
 }
src/Web.Api/Features/SalariesChartShare/GetChartSharePage/GetChartSharePageHandler.cs (1)

66-72: Minor: Consider HTML-encoding dynamic content.

Profession titles from the database are interpolated directly into HTML. While this is internal data, HTML-encoding would provide defense-in-depth against XSS if malicious content were ever stored.

🔎 Example using HtmlEncoder
using System.Text.Encodings.Web;
// ...
description += HtmlEncoder.Default.Encode(string.Join(", ", professions));
src/Web.Api/Features/SalariesHistoricalDataTemplates/DeleteTemplate/DeleteSalariesHistoricalDataRecordTemplateCommand.cs (1)

5-14: Consider using positional record syntax for brevity.

The command could be simplified using C#'s positional record syntax.

🔎 Simplified version
-public record DeleteSalariesHistoricalDataRecordTemplateCommand
-{
-    public DeleteSalariesHistoricalDataRecordTemplateCommand(
-        Guid id)
-    {
-        Id = id;
-    }
-
-    public Guid Id { get; }
-}
+public record DeleteSalariesHistoricalDataRecordTemplateCommand(Guid Id);
src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordTemplateEntityConfiguration.cs (1)

7-22: Configuration looks good overall.

The entity configuration correctly maps the table, primary key, required Name property, and JSON conversion for ProfessionIds. Consider adding a MaxLength constraint on Name to prevent unbounded string storage at the database level, which is a common best practice for string columns.

🔎 Optional improvement
         builder
             .Property(x => x.Name)
-            .IsRequired();
+            .IsRequired()
+            .HasMaxLength(256);
src/Web.Api/Features/SalariesHistoricalDataTemplates/DeleteTemplate/DeleteSalariesHistoricalDataRecordTemplateHandler.cs (1)

31-38: Manual record deletion is redundant with cascade delete configured.

The SalariesHistoricalDataRecordEntityConfiguration already configures OnDelete(DeleteBehavior.Cascade) for the relationship. EF Core will automatically delete related SalariesHistoricalDataRecords when the template is deleted. This manual fetch and removal is unnecessary and adds overhead (loading potentially many records into memory).

🔎 Suggested simplification
         var template = await _context.SalariesHistoricalDataRecordTemplates
                           .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken)
                       ?? throw NotFoundException.CreateFromEntity<SalariesHistoricalDataRecordTemplate>(request.Id);

-        var records = await _context.SalariesHistoricalDataRecords
-            .Where(x => x.TemplateId == template.Id)
-            .ToListAsync(cancellationToken);
-
-        if (records.Count > 0)
-        {
-            _context.SalariesHistoricalDataRecords.RemoveRange(records);
-        }
-
         _context.SalariesHistoricalDataRecordTemplates.Remove(template);

         await _context.SaveChangesAsync(cancellationToken);
src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/GetSalariesHistoricalDataRecordTemplatesHandler.cs (1)

21-29: Consider ordering by UpdatedAt for better UX.

The current ordering by CreatedAt descending shows newest templates first. Consider whether ordering by UpdatedAt descending would be more useful, as it would surface recently modified templates. This is a design choice depending on your use case.

🔎 Alternative ordering approach
         return await _context.SalariesHistoricalDataRecordTemplates
-            .OrderByDescending(x => x.CreatedAt)
+            .OrderByDescending(x => x.UpdatedAt)
             .Select(SalariesHistoricalDataRecordTemplateDto.Transform)
             .AsPaginatedAsync(request, cancellationToken);
src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/GetSalariesHistoricalDataRecordTemplatesHandlerTests.cs (1)

27-34: Consider using explicit timestamps instead of Task.Delay.

Using Task.Delay(10) to ensure different CreatedAt timestamps is fragile and can cause flaky tests on slower systems. If the SalariesHistoricalDataRecordTemplateFake or the domain entity allowed setting CreatedAt explicitly for testing purposes, it would be more reliable.

src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartHandler.cs (1)

30-54: In-memory grouping may impact performance with large datasets.

All historical records within the date range are loaded into memory before grouping by template. For date ranges with many records, this could cause memory pressure.

Consider whether server-side grouping is feasible, or add pagination/limits if the dataset could grow large.

src/Web.Api/Features/SalariesHistoricalDataTemplates/SalariesHistoricalDataTemplatesController.cs (1)

51-62: Consider using [HttpPut] for the Update operation.

Using [HttpPost("{id:guid}")] for updates is unconventional. RESTful conventions typically use:

  • PUT for full resource replacement
  • PATCH for partial updates

This doesn't break functionality but may confuse API consumers expecting standard REST semantics.

🔎 Proposed fix
-    [HttpPost("{id:guid}")]
+    [HttpPut("{id:guid}")]
     public async Task<SalariesHistoricalDataRecordTemplateDto> Update(
src/Infrastructure/Services/Telegram/UserCommands/TelegramBotUserCommandParameters.cs (1)

35-35: Consider caching the empty list to avoid repeated allocations.

Cities property creates a new empty list on each access. For frequently accessed properties, consider using a static readonly field.

🔎 Proposed fix
+    private static readonly List<KazakhstanCity> EmptyCities = new List<KazakhstanCity>(0);
+
-    public override List<KazakhstanCity> Cities => new List<KazakhstanCity>(0);
+    public override List<KazakhstanCity> Cities => EmptyCities;
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs (1)

18-28: Constructor follows established patterns but lacks input validation.

The constructor correctly initializes all properties and timestamps. Consider whether validation should be added for:

  • templateId (guard against Guid.Empty)
  • data (null check)

This depends on whether callers are expected to validate before construction or if the entity should be self-validating.

src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartResponse.cs (3)

68-78: Consider null safety for record.Data access.

The constructor directly accesses record.Data properties without null checks. If record.Data is null (e.g., due to database issues or deserialization), this will throw a NullReferenceException.

🔎 Proposed defensive null check
 public HistoricalDataPoint(
     SalariesHistoricalDataRecord record)
 {
+    ArgumentNullException.ThrowIfNull(record);
+    ArgumentNullException.ThrowIfNull(record.Data);
+
     Date = record.Date;
     MedianLocalSalary = record.Data.MedianLocalSalary;
     AverageLocalSalary = record.Data.AverageLocalSalary;
     MinLocalSalary = record.Data.MinLocalSalary;
     MaxLocalSalary = record.Data.MaxLocalSalary;
     TotalSalaryCount = record.Data.TotalSalaryCount;
     MedianLocalSalaryByGrade = record.Data.MedianLocalSalaryByGrade ?? new Dictionary<Domain.Enums.GradeGroup, double>();
 }

43-64: Mutable List<T> properties weaken record immutability.

While the record type provides immutable semantics for the properties themselves, exposing List<T> allows external callers to modify the collection contents. Consider using IReadOnlyList<T> for stronger immutability guarantees.

🔎 Proposed fix for immutability
 public record HistoricalDataByTemplate
 {
     public HistoricalDataByTemplate(
         Guid templateId,
         string name,
         List<long> professionIds,
         List<HistoricalDataPoint> dataPoints)
     {
         TemplateId = templateId;
         Name = name;
-        ProfessionIds = professionIds;
-        DataPoints = dataPoints;
+        ProfessionIds = professionIds.AsReadOnly();
+        DataPoints = dataPoints.AsReadOnly();
     }
 
     public Guid TemplateId { get; }
 
     public string Name { get; }
 
-    public List<long> ProfessionIds { get; }
+    public IReadOnlyList<long> ProfessionIds { get; }
 
-    public List<HistoricalDataPoint> DataPoints { get; }
+    public IReadOnlyList<HistoricalDataPoint> DataPoints { get; }
 }

25-25: Same immutability consideration for Templates property.

Consistent with HistoricalDataByTemplate, consider using IReadOnlyList<HistoricalDataByTemplate> for the Templates property.

src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/CreateSalariesHistoricalDataRecordTemplateHandlerTests.cs (1)

53-123: Consider consolidating name validation tests with [Theory].

The three tests (Handle_EmptyName_ThrowsBadRequestException, Handle_NullName_ThrowsBadRequestException, Handle_WhitespaceName_ThrowsBadRequestException) have identical structure with only the input varying. Using [Theory] with [InlineData] would reduce duplication.

🔎 Proposed consolidation using Theory
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData("   ")]
public async Task Handle_InvalidName_ThrowsBadRequestException(string? invalidName)
{
    // Arrange
    await using var context = new InMemoryDatabaseContext();

    var profession = context.Professions.First(x => x.Id == 1);

    var handler = new CreateSalariesHistoricalDataRecordTemplateHandler(context);

    var command = new CreateSalariesHistoricalDataRecordTemplateCommand(
        new CreateSalariesHistoricalDataRecordTemplateBodyRequest
        {
            Name = invalidName,
            ProfessionIds = new List<long> { profession.Id }
        });

    // Act & Assert
    var exception = await Assert.ThrowsAsync<BadRequestException>(
        async () => await handler.Handle(command, default));

    Assert.Contains("Name cannot be null or empty", exception.Message);
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02e0d24 and 563e7be.

⛔ Files ignored due to path filters (1)
  • src/Infrastructure/Migrations/20251226151311_SalariesHistoricalEntitiesAdded.Designer.cs is excluded by !**/*.Designer.cs, !**/Migrations/*.Designer.cs
📒 Files selected for processing (49)
  • .gitignore
  • src/Domain/Domain.csproj
  • src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs
  • src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs
  • src/Domain/ValueObjects/ISalariesChartQueryParams.cs
  • src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs
  • src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordEntityConfiguration.cs
  • src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordTemplateEntityConfiguration.cs
  • src/Infrastructure/Database/DatabaseContext.cs
  • src/Infrastructure/Infrastructure.csproj
  • src/Infrastructure/Migrations/20251226151311_SalariesHistoricalEntitiesAdded.cs
  • src/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
  • src/Infrastructure/Salaries/SalariesChartPageLink.cs
  • src/Infrastructure/Salaries/SalariesForChartQuery.cs
  • src/Infrastructure/Services/Telegram/UserCommands/TelegramBotUserCommandParameters.cs
  • src/TestUtils/Fakes/SalariesHistoricalDataRecordTemplateFake.cs
  • src/TestUtils/TestUtils.csproj
  • src/Web.Api.Tests/Features/BackgroundJobs/SalariesHistoricalDataJobTests.cs
  • src/Web.Api.Tests/Features/Salaries/GetSalariesHistoricalChart/Charts/SalariesCountWeekByWeekChartTests.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/CreateSalariesHistoricalDataRecordTemplateHandlerTests.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/DeleteSalariesHistoricalDataRecordTemplateHandlerTests.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/GetSalariesHistoricalDataRecordTemplatesHandlerTests.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/UpdateSalariesHistoricalDataRecordTemplateHandlerTests.cs
  • src/Web.Api/Features/BackgroundJobs/Salaries/SalariesHistoricalDataJob.cs
  • src/Web.Api/Features/Historical/GetSalariesHistoricalChart/Charts/SalariesCountWeekByWeekChart.cs
  • src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartHandler.cs
  • src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartResponse.cs
  • src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs
  • src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQuery.cs
  • src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQueryHandler.cs
  • src/Web.Api/Features/Salaries/GetSalariesChart/GetSalariesChartHandler.cs
  • src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs
  • src/Web.Api/Features/SalariesChartShare/GetChartSharePage/GetChartSharePageHandler.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateBodyRequest.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateCommand.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateHandler.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/DeleteTemplate/DeleteSalariesHistoricalDataRecordTemplateCommand.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/DeleteTemplate/DeleteSalariesHistoricalDataRecordTemplateHandler.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/GetSalariesHistoricalDataRecordTemplatesHandler.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/GetSalariesHistoricalDataRecordTemplatesQuery.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/SalariesHistoricalDataRecordTemplateDto.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/SalariesHistoricalDataTemplatesController.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/Shared/UpdateSalariesHistoricalDataRecordTemplateBodyRequest.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateCommand.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateHandler.cs
  • src/Web.Api/Features/Telegram/ProcessSalariesRelatedMessage/ProcessSalariesRelatedTelegramMessageHandler.cs
  • src/Web.Api/Setup/ScheduleConfig.cs
  • src/Web.Api/Startup.cs
  • src/Web.Api/Web.Api.csproj
💤 Files with no reviewable changes (5)
  • src/Web.Api/Features/Telegram/ProcessSalariesRelatedMessage/ProcessSalariesRelatedTelegramMessageHandler.cs
  • src/Web.Api/Startup.cs
  • src/Domain/ValueObjects/ISalariesChartQueryParams.cs
  • src/Web.Api.Tests/Features/Salaries/GetSalariesHistoricalChart/Charts/SalariesCountWeekByWeekChartTests.cs
  • src/Web.Api/Features/Historical/GetSalariesHistoricalChart/Charts/SalariesCountWeekByWeekChart.cs
🧰 Additional context used
📓 Path-based instructions (4)
**/*.cs

⚙️ CodeRabbit configuration file

**/*.cs: Review C# code for:

  • SOLID principles and clean architecture adherence
  • Proper async/await usage and avoiding async void
  • Null safety and proper use of nullable reference types
  • Dependency injection best practices
  • Exception handling patterns
  • Memory management and IDisposable implementation
  • LINQ query optimization

Files:

  • src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordTemplateEntityConfiguration.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/Shared/UpdateSalariesHistoricalDataRecordTemplateBodyRequest.cs
  • src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordEntityConfiguration.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/GetSalariesHistoricalDataRecordTemplatesQuery.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateHandler.cs
  • src/Infrastructure/Salaries/SalariesForChartQuery.cs
  • src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs
  • src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartHandler.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateCommand.cs
  • src/Infrastructure/Salaries/SalariesChartPageLink.cs
  • src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/UpdateSalariesHistoricalDataRecordTemplateHandlerTests.cs
  • src/Web.Api/Features/BackgroundJobs/Salaries/SalariesHistoricalDataJob.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/GetSalariesHistoricalDataRecordTemplatesHandler.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/DeleteTemplate/DeleteSalariesHistoricalDataRecordTemplateCommand.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateHandler.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/SalariesHistoricalDataTemplatesController.cs
  • src/Web.Api.Tests/Features/BackgroundJobs/SalariesHistoricalDataJobTests.cs
  • src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs
  • src/Infrastructure/Migrations/20251226151311_SalariesHistoricalEntitiesAdded.cs
  • src/TestUtils/Fakes/SalariesHistoricalDataRecordTemplateFake.cs
  • src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs
  • src/Infrastructure/Database/DatabaseContext.cs
  • src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartResponse.cs
  • src/Web.Api/Features/Salaries/GetSalariesChart/GetSalariesChartHandler.cs
  • src/Web.Api/Setup/ScheduleConfig.cs
  • src/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/DeleteSalariesHistoricalDataRecordTemplateHandlerTests.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateCommand.cs
  • src/Infrastructure/Services/Telegram/UserCommands/TelegramBotUserCommandParameters.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateBodyRequest.cs
  • src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQueryHandler.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/DeleteTemplate/DeleteSalariesHistoricalDataRecordTemplateHandler.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/GetSalariesHistoricalDataRecordTemplatesHandlerTests.cs
  • src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs
  • src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/SalariesHistoricalDataRecordTemplateDto.cs
  • src/Web.Api/Features/SalariesChartShare/GetChartSharePage/GetChartSharePageHandler.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/CreateSalariesHistoricalDataRecordTemplateHandlerTests.cs
  • src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQuery.cs
**/*Tests.cs

⚙️ CodeRabbit configuration file

**/*Tests.cs: Review test files for:

  • Adequate test coverage
  • Proper use of xUnit/NUnit/MSTest assertions
  • Mock objects with Moq or NSubstitute
  • Arrange-Act-Assert pattern
  • Integration test isolation

Files:

  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/UpdateSalariesHistoricalDataRecordTemplateHandlerTests.cs
  • src/Web.Api.Tests/Features/BackgroundJobs/SalariesHistoricalDataJobTests.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/DeleteSalariesHistoricalDataRecordTemplateHandlerTests.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/GetSalariesHistoricalDataRecordTemplatesHandlerTests.cs
  • src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/CreateSalariesHistoricalDataRecordTemplateHandlerTests.cs
**/Migrations/**/*.cs

⚙️ CodeRabbit configuration file

**/Migrations/**/*.cs: Review EF Core migrations for:

  • Data integrity preservation
  • Proper index creation
  • Reversible migrations where possible

Files:

  • src/Infrastructure/Migrations/20251226151311_SalariesHistoricalEntitiesAdded.cs
  • src/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
**/Services/**/*.cs

⚙️ CodeRabbit configuration file

**/Services/**/*.cs: Review service layer for:

  • Single responsibility principle
  • Proper abstraction through interfaces
  • Transaction management
  • Business logic encapsulation

Files:

  • src/Infrastructure/Services/Telegram/UserCommands/TelegramBotUserCommandParameters.cs
🧠 Learnings (3)
📚 Learning: 2025-07-09T11:00:06.133Z
Learnt from: maximgorbatyuk
Repo: Techinterview-space/web-api PR: 153
File: src/Web.Api/Services/CompanyReviews/CompanyReviewsSubscriptionService.cs:163-166
Timestamp: 2025-07-09T11:00:06.133Z
Learning: In the codebase, SubscriptionTelegramMessage is a unified entity that handles messages for both salary subscriptions and company reviews subscriptions. There is only one DbSet called SalariesSubscriptionTelegramMessages in the DatabaseContext that stores messages for both subscription types. The entity has separate foreign keys (SalarySubscriptionId and CompanyReviewsSubscriptionId) and constructors for each subscription type.

Applied to files:

  • src/Infrastructure/Database/DatabaseContext.cs
  • src/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
📚 Learning: 2025-05-31T18:01:06.749Z
Learnt from: maximgorbatyuk
Repo: Techinterview-space/web-api PR: 107
File: src/Domain/Entities/Users/UserEmail.cs:21-30
Timestamp: 2025-05-31T18:01:06.749Z
Learning: In the Domain.Entities.Users namespace, the team prefers not to set navigation properties in entity constructors and considers the current validation approach sufficient without additional data annotation attributes.

Applied to files:

  • src/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
📚 Learning: 2025-06-19T09:01:04.940Z
Learnt from: maximgorbatyuk
Repo: Techinterview-space/web-api PR: 122
File: src/Domain/Entities/Github/GithubProfileBotChat.cs:28-28
Timestamp: 2025-06-19T09:01:04.940Z
Learning: In the GithubProfileBotChat entity (src/Domain/Entities/Github/GithubProfileBotChat.cs), the MessagesCount is intentionally initialized to 1 in the constructor because the entity creation itself implies that at least one message has been sent to trigger the chat creation.

Applied to files:

  • src/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs
🧬 Code graph analysis (26)
src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordTemplateEntityConfiguration.cs (1)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordEntityConfiguration.cs (3)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs (3)
  • SalariesHistoricalDataRecord (6-33)
  • SalariesHistoricalDataRecord (18-28)
  • SalariesHistoricalDataRecord (30-32)
src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordTemplateEntityConfiguration.cs (1)
  • Configure (9-22)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateHandler.cs (5)
src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateCommand.cs (1)
  • CreateSalariesHistoricalDataRecordTemplateCommand (6-11)
src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/SalariesHistoricalDataRecordTemplateDto.cs (2)
  • SalariesHistoricalDataRecordTemplateDto (10-12)
  • SalariesHistoricalDataRecordTemplateDto (14-22)
src/Infrastructure/Database/DatabaseContext.cs (8)
  • DatabaseContext (24-233)
  • DatabaseContext (26-29)
  • Task (99-111)
  • Task (113-125)
  • Task (127-133)
  • Task (145-156)
  • Task (158-178)
  • Task (186-191)
src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateHandler.cs (1)
  • Task (25-62)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Infrastructure/Salaries/SalariesForChartQuery.cs (1)
src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (2)
  • SalariesChartQueryParamsBase (11-13)
  • SalariesChartQueryParamsBase (15-33)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (2)
src/Domain/Entities/HasDatesBase.cs (1)
  • HasDatesBase (6-21)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs (3)
  • SalariesHistoricalDataRecord (6-33)
  • SalariesHistoricalDataRecord (18-28)
  • SalariesHistoricalDataRecord (30-32)
src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartHandler.cs (2)
src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartResponse.cs (5)
  • GetSalariesHistoricalChartResponse (11-13)
  • GetSalariesHistoricalChartResponse (15-23)
  • GetSalariesHistoricalChartResponse (31-40)
  • HistoricalDataPoint (68-78)
  • HistoricalDataByTemplate (45-55)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Infrastructure/Salaries/SalariesChartPageLink.cs (2)
src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (2)
  • SalariesChartQueryParamsBase (11-13)
  • SalariesChartQueryParamsBase (15-33)
src/Web.Api/Services/Salaries/SalariesSubscriptionService.cs (1)
  • SalariesChartPageLink (310-317)
src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (2)
src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQuery.cs (1)
  • Domain (37-48)
src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs (1)
  • Domain (37-48)
src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/UpdateSalariesHistoricalDataRecordTemplateHandlerTests.cs (1)
src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateCommand.cs (1)
  • UpdateSalariesHistoricalDataRecordTemplateCommand (11-18)
src/Web.Api/Features/BackgroundJobs/Salaries/SalariesHistoricalDataJob.cs (2)
src/Infrastructure/Database/DatabaseContext.cs (2)
  • DatabaseContext (24-233)
  • DatabaseContext (26-29)
src/Infrastructure/Salaries/SalariesForChartQuery.cs (4)
  • SalariesForChartQuery (40-68)
  • SalariesForChartQuery (70-80)
  • SalariesForChartQuery (82-99)
  • SalariesForChartQuery (101-106)
src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateHandler.cs (7)
src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateCommand.cs (1)
  • UpdateSalariesHistoricalDataRecordTemplateCommand (11-18)
src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/SalariesHistoricalDataRecordTemplateDto.cs (2)
  • SalariesHistoricalDataRecordTemplateDto (10-12)
  • SalariesHistoricalDataRecordTemplateDto (14-22)
src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateHandler.cs (1)
  • Task (25-51)
src/Infrastructure/Salaries/SalariesForChartQuery.cs (1)
  • Task (196-200)
src/Web.Api/Features/Salaries/GetSalariesChart/GetSalariesChartHandler.cs (3)
  • Task (48-179)
  • Task (181-186)
  • Task (306-355)
src/Domain/Validation/EntityValidator.cs (1)
  • ThrowIfInvalid (36-42)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (4)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
  • Update (34-41)
src/Web.Api.Tests/Features/BackgroundJobs/SalariesHistoricalDataJobTests.cs (3)
src/TestUtils/Fakes/SalariesHistoricalDataRecordTemplateFake.cs (2)
  • SalariesHistoricalDataRecordTemplateFake (8-26)
  • SalariesHistoricalDataRecordTemplateFake (10-17)
src/Web.Api/Features/BackgroundJobs/Salaries/SalariesHistoricalDataJob.cs (2)
  • SalariesHistoricalDataJob (17-134)
  • SalariesHistoricalDataJob (22-28)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs (3)
  • SalariesHistoricalDataRecord (6-33)
  • SalariesHistoricalDataRecord (18-28)
  • SalariesHistoricalDataRecord (30-32)
src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs (2)
src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQuery.cs (1)
  • Domain (37-48)
src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (2)
  • SalariesChartQueryParamsBase (11-13)
  • SalariesChartQueryParamsBase (15-33)
src/TestUtils/Fakes/SalariesHistoricalDataRecordTemplateFake.cs (2)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Infrastructure/Database/DatabaseContext.cs (8)
  • Task (99-111)
  • Task (113-125)
  • Task (127-133)
  • Task (145-156)
  • Task (158-178)
  • Task (186-191)
  • DatabaseContext (24-233)
  • DatabaseContext (26-29)
src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs (2)
src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (1)
  • GetKeyPostfix (54-59)
src/Web.Api/Features/Salaries/GetSalariesChart/Charts/SalariesChartQueryParams.cs (1)
  • GetKeyPostfix (40-45)
src/Infrastructure/Database/DatabaseContext.cs (2)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs (3)
  • SalariesHistoricalDataRecord (6-33)
  • SalariesHistoricalDataRecord (18-28)
  • SalariesHistoricalDataRecord (30-32)
src/Web.Api/Features/Historical/GetSalariesHistoricalChart/GetSalariesHistoricalChartResponse.cs (2)
src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs (1)
  • Domain (37-48)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs (3)
  • SalariesHistoricalDataRecord (6-33)
  • SalariesHistoricalDataRecord (18-28)
  • SalariesHistoricalDataRecord (30-32)
src/Web.Api/Features/Salaries/GetSalariesChart/GetSalariesChartHandler.cs (1)
src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (2)
  • SalariesChartQueryParamsBase (11-13)
  • SalariesChartQueryParamsBase (15-33)
src/Web.Api/Setup/ScheduleConfig.cs (2)
src/Web.Api/Features/BackgroundJobs/Salaries/SalariesHistoricalDataJob.cs (2)
  • SalariesHistoricalDataJob (17-134)
  • SalariesHistoricalDataJob (22-28)
src/Web.Api/Features/BackgroundJobs/Salaries/SalariesSubscriptionPublishMessageJob.cs (2)
  • SalariesSubscriptionPublishMessageJob (9-35)
  • SalariesSubscriptionPublishMessageJob (14-20)
src/Infrastructure/Services/Telegram/UserCommands/TelegramBotUserCommandParameters.cs (1)
src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (2)
  • SalariesChartQueryParamsBase (11-13)
  • SalariesChartQueryParamsBase (15-33)
src/Web.Api/Features/SalariesHistoricalDataTemplates/DeleteTemplate/DeleteSalariesHistoricalDataRecordTemplateHandler.cs (2)
src/Web.Api/Features/SalariesHistoricalDataTemplates/DeleteTemplate/DeleteSalariesHistoricalDataRecordTemplateCommand.cs (1)
  • DeleteSalariesHistoricalDataRecordTemplateCommand (7-11)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/GetSalariesHistoricalDataRecordTemplatesHandlerTests.cs (2)
src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/CreateSalariesHistoricalDataRecordTemplateHandlerTests.cs (7)
  • Fact (16-51)
  • Fact (53-75)
  • Fact (77-99)
  • Fact (101-123)
  • Fact (125-145)
  • Fact (147-167)
  • Fact (169-191)
src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/DeleteSalariesHistoricalDataRecordTemplateHandlerTests.cs (5)
  • Fact (18-45)
  • Fact (47-100)
  • Fact (102-118)
  • Fact (120-160)
  • Fact (162-207)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs (2)
src/Domain/Entities/HasDatesBase.cs (1)
  • HasDatesBase (6-21)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/SalariesHistoricalDataRecordTemplateDto.cs (1)
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (3)
  • SalariesHistoricalDataRecordTemplate (7-46)
  • SalariesHistoricalDataRecordTemplate (17-32)
  • SalariesHistoricalDataRecordTemplate (43-45)
src/Web.Api/Features/SalariesChartShare/GetChartSharePage/GetChartSharePageHandler.cs (2)
src/Infrastructure/Salaries/SalariesChartPageLink.cs (3)
  • SalariesChartPageLink (14-21)
  • SalariesChartPageLink (23-30)
  • SalariesChartPageLink (32-36)
src/Web.Api/Services/Salaries/SalariesSubscriptionService.cs (1)
  • SalariesChartPageLink (310-317)
src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQuery.cs (2)
src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs (1)
  • Domain (37-48)
src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs (2)
  • SalariesChartQueryParamsBase (11-13)
  • SalariesChartQueryParamsBase (15-33)
🔇 Additional comments (50)
src/TestUtils/TestUtils.csproj (1)

10-12: LGTM. These are the latest versions (10.0.1 published December 9, 2025) with no known security vulnerabilities. The patch updates maintain consistent versioning across the related Entity Framework Core packages.

src/Domain/Domain.csproj (1)

9-9: The Otp.NET 1.4.1 update is verified as safe and complete. Version 1.4.1 is a stable release (December 1, 2025) with no known security vulnerabilities, and the changelog indicates it contains verification window fixes. Since only this project uses Otp.NET, no consistency updates are needed across other projects.

.gitignore (1)

38-38: LGTM!

Adding the AI-generated analysis artifact to gitignore is appropriate.

src/Infrastructure/Infrastructure.csproj (1)

19-47: All package versions referenced in the Infrastructure.csproj file have been verified to exist in the NuGet repository and are free from known security vulnerabilities. No action required.

src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQueryHandler.cs (1)

24-29: LGTM!

The refactored call to ApplyFilters using request.CreateDatabaseQueryParams() aligns with the broader architectural shift to domain-based query parameter objects, maintaining clean separation between API and domain layers.

src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/GetSalariesHistoricalDataRecordTemplatesQuery.cs (1)

1-5: LGTM!

Clean and minimal query definition following CQRS patterns. The inheritance from PageModel provides pagination support.

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

53-54: LGTM!

The refactored code correctly uses CreateDatabaseQueryParams() to produce the database query parameters and passes them to SalariesChartPageLink, consistent with the broader architectural changes in this PR.

src/Infrastructure/Database/DatabaseContext.cs (1)

95-97: LGTM!

The new DbSet properties for SalariesHistoricalDataRecordTemplates and SalariesHistoricalDataRecords follow the established pattern in the codebase and are properly positioned within the DbContext.

src/Web.Api/Features/BackgroundJobs/Salaries/SalariesHistoricalDataJob.cs (1)

126-133: No null reference issues in this code.

The main SalariesForChartQuery constructor (lines 40-63) uses null coalescing operators (??) to safely initialize all list properties to empty lists if null values are passed, regardless of what SalariesForChartRequest initializes. All subsequent usage patterns safely check .Count or call .Any(), which are safe on empty lists.

Likely an incorrect or invalid review comment.

src/Web.Api/Features/Salaries/GetSalariesChart/GetSalariesChartHandler.cs (1)

69-72: The CreateDatabaseQueryParams() call is necessary. Web.Api.Features.Salaries.SalariesChartQueryParamsBase and Domain.ValueObjects.SalariesChartQueryParamsBase are distinct types in different namespaces. The Web.Api version is a request DTO with ASP.NET Core binding attributes ([FromQuery]), while the Domain version is a value object for database queries. The method correctly converts from the Web.Api layer type to the Domain layer type—this is proper clean architecture layering, not redundancy.

Likely an incorrect or invalid review comment.

src/Infrastructure/Database/Config/HistoricalRecords/SalariesHistoricalDataRecordEntityConfiguration.cs (1)

7-32: Well-structured entity configuration.

The configuration correctly establishes the table mapping, primary key, JSON conversion for the Data property, and relationship with cascade delete behavior. The indexes on Date and TemplateId are appropriate for the expected query patterns (filtering by template and time-based queries).

src/Web.Api/Setup/ScheduleConfig.cs (4)

24-25: Service registration looks correct.

The SalariesHistoricalDataJob is properly registered as a transient service, consistent with other job registrations in this file.


32-32: Good practice caching the debugger state.

Caching Debugger.IsAttached at startup ensures consistent behavior throughout the scheduler configuration. This avoids potential issues if the debugger state were to change after initial configuration.


40-43: Daily schedule for historical data job is appropriate.

Running at 13:00 daily with PreventOverlapping is a reasonable approach for collecting historical salary data. The job implementation already handles idempotency by checking for existing records for the current day.


81-85: Debug schedule follows existing pattern.

The every-minute debug schedule gated by isDebuggerAttached is consistent with other debug schedules in this file (lines 69-79).

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

70-79: Type refactoring from interface to concrete type.

The constructor parameter change from ISalariesChartQueryParams to SalariesChartQueryParamsBase is consistent with the broader refactoring in this PR. The concrete base type provides all the same properties, and the constructor logic remains unchanged.


82-99: Secondary constructor correctly updated.

This constructor also properly uses SalariesChartQueryParamsBase and correctly accesses all required properties from the request object to pass to the primary constructor.

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

37-48: Clean factory method implementation.

The CreateDatabaseQueryParams() method provides a clean separation between the API query model and the domain value object. All filter properties are correctly mapped to the SalariesChartQueryParamsBase constructor.

src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateCommand.cs (1)

6-18: Command structure follows CQRS pattern.

The record correctly extends the body request type and adds TemplateId for identifying the target entity. The constructor properly copies properties from the incoming request, making it easy to construct from controller route parameters and request body.

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

8-8: Type refactoring from interface to concrete base type is compatible and correct.

The field and constructor parameter types are correctly updated to use SalariesChartQueryParamsBase. TelegramBotUserCommandParameters inherits from SalariesChartQueryParamsBase, confirming full compatibility. The internal logic accessing SelectedProfessionIds and Grade remains valid as these properties are inherited from the base type.

src/Infrastructure/Migrations/20251226151311_SalariesHistoricalEntitiesAdded.cs (4)

14-27: LGTM! Template table structure is well-designed.

The table structure appropriately defines the template entity with required Name and optional ProfessionIds (likely JSON-serialized). Timestamps are properly included.


29-49: LGTM! Cascade delete appropriately maintains referential integrity.

The foreign key relationship with cascade delete ensures that deleting a template will automatically remove all associated historical records, preventing orphaned data.


51-59: LGTM! Indexes support expected query patterns.

The indexes on Date and TemplateId will optimize common queries, particularly the daily record existence checks performed by the background job.


62-70: LGTM! Down method correctly reverses the migration.

Tables are dropped in the proper order (child before parent) to avoid foreign key constraint violations.

src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/DeleteSalariesHistoricalDataRecordTemplateHandlerTests.cs (5)

18-45: LGTM! Well-structured test with proper isolation.

The test correctly uses ChangeTracker.Clear() to ensure the handler fetches fresh data from the database, preventing false positives from cached entities.


47-100: LGTM! Comprehensive test of cascade delete behavior.

The test properly verifies that deleting a template cascades to delete all associated historical records, validating the database foreign key constraints work as expected.


102-118: LGTM! Proper error handling test.

The test correctly verifies that attempting to delete a non-existent template throws NotFoundException with the appropriate error message.


120-160: LGTM! Excellent test for operation isolation.

The test ensures that deleting one template doesn't inadvertently affect other templates, validating proper scoping of the delete operation.


162-207: LGTM! Comprehensive test of selective cascade delete.

The test excellently verifies that cascade delete only affects records associated with the deleted template, leaving other templates' records intact.

src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/UpdateSalariesHistoricalDataRecordTemplateHandlerTests.cs (3)

17-62: LGTM! Comprehensive update test with thorough assertions.

The test properly verifies both the returned DTO and the persisted entity, ensuring the update operation works correctly in all aspects.


161-185: LGTM! Proper error handling for non-existent templates.

The test correctly verifies that updating a non-existent template throws NotFoundException with the appropriate error message.


187-249: LGTM! Comprehensive validation tests for profession IDs.

Both tests properly verify business rules for profession IDs, ensuring empty lists and invalid IDs are rejected with appropriate error messages.

src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateHandler.cs (2)

29-42: LGTM! Profession validation logic is sound.

The validation correctly ensures profession IDs exist before creating the template. The count comparison efficiently detects invalid IDs without needing to identify specific ones.

Note: Name validation is handled by the entity constructor, which is an acceptable approach per the team's coding practices.


44-51: LGTM! Entity creation follows best practices.

The handler correctly creates the entity using the validated profession IDs and properly persists it. Using the validated professions list from the database query ensures data integrity.

src/Web.Api.Tests/Features/BackgroundJobs/SalariesHistoricalDataJobTests.cs (3)

20-79: LGTM! Solid happy path test.

The test properly verifies the background job creates a historical record when salaries exist, with appropriate assertions on the record's properties.


81-118: LGTM! Comprehensive edge case test with proper logging verification.

The test correctly verifies that no record is created when no salaries exist and that an appropriate informational log message is emitted.


120-197: LGTM! Important idempotency test.

The test ensures the background job correctly detects existing records for the current day and doesn't create duplicates, which is critical for data integrity in a daily scheduled job.

src/Infrastructure/Migrations/DatabaseContextModelSnapshot.cs (2)

372-424: LGTM! Entity definitions match migration structure.

The model snapshot correctly reflects the new historical data entities and their relationships, including proper cascade delete behavior and indexes.

Also applies to: 1635-1644, 1887-1890


20-20: EF Core 10.0.1 upgrade is properly configured; verify parameterized collection behavior.

The ProductVersion upgrade from 9.0.2 to 10.0.1 is intentional—all EF Core packages (Microsoft.EntityFrameworkCore, Microsoft.EntityFrameworkCore.Design, Microsoft.EntityFrameworkCore.Relational) are consistently updated to 10.0.1, and Npgsql.EntityFrameworkCore.PostgreSQL is updated to 10.0.0.

Most EF Core 10.0 breaking changes do not apply to this PostgreSQL-based codebase (ExecuteUpdate API changes, complex type column naming, discriminator conventions, SQLite date/time handling, and Azure SQL JSON columns are not relevant). However, verify that parameterized collection translation behavior—which changed from JSON array parameters to multiple scalar parameters by default in EF Core 10.0—does not negatively impact query performance or correctness for the collection-based filters in handlers like UpdateSalariesHistoricalDataRecordTemplateHandler and CreateSalariesHistoricalDataRecordTemplateHandler (e.g., .Where(x => request.ProfessionIds.Contains(x.Id))).

src/Web.Api/Features/SalariesHistoricalDataTemplates/GetTemplates/SalariesHistoricalDataRecordTemplateDto.cs (2)

8-32: LGTM! Well-designed DTO with proper immutability.

The record type with init accessors provides value semantics and immutability. The parameterless constructor supports serialization scenarios while the mapping constructor enables easy conversion from entities.


34-41: LGTM! Efficient projection expression for EF Core.

The static Transform expression enables efficient database-side projection in LINQ queries, avoiding the N+1 problem and minimizing data transfer. The expression tree syntax is correct and will translate properly to SQL.

src/TestUtils/Fakes/SalariesHistoricalDataRecordTemplateFake.cs (1)

1-26: LGTM!

The fake implementation follows the test utility pattern correctly. The constructor properly handles the nullable professionIds with a null-coalescing fallback, and PleaseAsync correctly persists and returns the entity.

Minor consideration: The method could benefit from a null check on context, but this is acceptable for test utilities where the caller controls the input.

src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs (1)

17-32: LGTM!

The constructor properly validates the name, generates a new GUID for Id, initializes the navigation collection to an empty list (preventing null reference issues), and uses UTC timestamps consistently.

src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/GetSalariesHistoricalDataRecordTemplatesHandlerTests.cs (1)

11-181: Good test coverage following AAA pattern.

The tests properly cover:

  • Ordering by CreatedAt descending
  • Empty result handling
  • Pagination behavior
  • ProfessionIds mapping

The use of context.ChangeTracker.Clear() before assertions ensures tests verify actual database queries rather than cached entities.

src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateHandler.cs (1)

25-62: Well-structured handler following validation patterns.

The handler correctly:

  • Validates request and ProfessionIds upfront
  • Checks template existence before update
  • Validates profession IDs against the database
  • Uses consistent exception types

Note: This relies on the domain entity's Update method to validate the name. Ensure that validation is added there (as noted in the domain entity review).

src/Web.Api/Features/SalariesHistoricalDataTemplates/SalariesHistoricalDataTemplatesController.cs (1)

17-49: LGTM!

The controller properly:

  • Uses [HasAnyRole(Role.Admin)] for authorization
  • Passes CancellationToken to all async operations
  • Uses the mediator pattern via _serviceProvider.HandleBy
  • Returns appropriate types for each operation
src/Infrastructure/Services/Telegram/UserCommands/TelegramBotUserCommandParameters.cs (1)

8-43: Migration to SalariesChartQueryParamsBase looks correct.

The record properly:

  • Inherits from the concrete base class instead of the interface
  • Overrides necessary properties with appropriate fixed values
  • Maintains existing functionality for profession-based filtering
src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecord.cs (1)

6-33: LGTM!

The entity follows the established domain patterns:

  • Inherits from HasDatesBase and implements IHasIdBase<Guid>
  • Uses protected setters for encapsulation
  • Includes protected parameterless constructor for EF Core
  • Navigation property correctly links to the template entity
src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/CreateSalariesHistoricalDataRecordTemplateHandlerTests.cs (2)

16-51: Well-structured happy path test.

Good coverage of the creation flow with proper verification of both the returned DTO and the persisted entity. The test correctly validates the key properties (Id, Name, ProfessionIds) and confirms database persistence.


125-191: Good validation coverage; no null test needed.

The tests cover empty, invalid, and partially invalid profession IDs well. The ProfessionIds property is non-nullable by design (with [Required] attribute and default initialization), so a null test is unnecessary.

Comment on lines +34 to +41
public void Update(
string name,
List<long> professionIds)
{
Name = name;
ProfessionIds = professionIds;
UpdatedAt = DateTimeOffset.UtcNow;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Update method lacks name validation present in constructor.

The constructor validates name by trimming and checking for null/empty (lines 21-25), throwing BadRequestException. However, Update directly assigns the name without this validation, allowing invalid names to be set.

🔎 Proposed fix to add consistent validation
 public void Update(
     string name,
     List<long> professionIds)
 {
+    name = name?.Trim();
+    if (string.IsNullOrEmpty(name))
+    {
+        throw new BadRequestException("Name cannot be null or empty.");
+    }
+
     Name = name;
     ProfessionIds = professionIds;
     UpdatedAt = DateTimeOffset.UtcNow;
 }
📝 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 void Update(
string name,
List<long> professionIds)
{
Name = name;
ProfessionIds = professionIds;
UpdatedAt = DateTimeOffset.UtcNow;
}
public void Update(
string name,
List<long> professionIds)
{
name = name?.Trim();
if (string.IsNullOrEmpty(name))
{
throw new BadRequestException("Name cannot be null or empty.");
}
Name = name;
ProfessionIds = professionIds;
UpdatedAt = DateTimeOffset.UtcNow;
}
🤖 Prompt for AI Agents
In src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs
around lines 34 to 41, the Update method assigns Name directly without the same
trim-and-null/empty validation present in the constructor; modify Update to trim
the incoming name, validate that the trimmed string is not null or empty, and
throw the same BadRequestException used in the constructor on invalid input,
then assign the trimmed value to Name and update UpdatedAt (leave ProfessionIds
assignment as-is).

Comment thread src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs
Comment on lines +64 to +159
[Fact]
public async Task Handle_EmptyName_ThrowsEntityInvalidException()
{
// Arrange
await using var context = new InMemoryDatabaseContext();

var profession = context.Professions.First(x => x.Id == 1);

var existingTemplate = await new SalariesHistoricalDataRecordTemplateFake(
"Original Name",
new List<long> { profession.Id })
.PleaseAsync(context);

context.ChangeTracker.Clear();

var handler = new UpdateSalariesHistoricalDataRecordTemplateHandler(context);

var command = new UpdateSalariesHistoricalDataRecordTemplateCommand(
existingTemplate.Id,
new UpdateSalariesHistoricalDataRecordTemplateBodyRequest
{
Name = string.Empty,
ProfessionIds = new List<long> { profession.Id }
});

// Act & Assert
var exception = await Assert.ThrowsAsync<Domain.Validation.Exceptions.EntityInvalidException>(
async () => await handler.Handle(command, default));

Assert.Contains("Name is invalid", exception.Message);
}

[Fact]
public async Task Handle_NullName_ThrowsEntityInvalidException()
{
// Arrange
await using var context = new InMemoryDatabaseContext();

var profession = context.Professions.First(x => x.Id == 1);

var existingTemplate = await new SalariesHistoricalDataRecordTemplateFake(
"Original Name",
new List<long> { profession.Id })
.PleaseAsync(context);

context.ChangeTracker.Clear();

var handler = new UpdateSalariesHistoricalDataRecordTemplateHandler(context);

var command = new UpdateSalariesHistoricalDataRecordTemplateCommand(
existingTemplate.Id,
new UpdateSalariesHistoricalDataRecordTemplateBodyRequest
{
Name = null,
ProfessionIds = new List<long> { profession.Id }
});

// Act & Assert
var exception = await Assert.ThrowsAsync<Domain.Validation.Exceptions.EntityInvalidException>(
async () => await handler.Handle(command, default));

Assert.Contains("Name is invalid", exception.Message);
}

[Fact]
public async Task Handle_WhitespaceName_ThrowsEntityInvalidException()
{
// Arrange
await using var context = new InMemoryDatabaseContext();

var profession = context.Professions.First(x => x.Id == 1);

var existingTemplate = await new SalariesHistoricalDataRecordTemplateFake(
"Original Name",
new List<long> { profession.Id })
.PleaseAsync(context);

context.ChangeTracker.Clear();

var handler = new UpdateSalariesHistoricalDataRecordTemplateHandler(context);

var command = new UpdateSalariesHistoricalDataRecordTemplateCommand(
existingTemplate.Id,
new UpdateSalariesHistoricalDataRecordTemplateBodyRequest
{
Name = " ",
ProfessionIds = new List<long> { profession.Id }
});

// Act & Assert
// Whitespace-only name fails [Required] validation
var exception = await Assert.ThrowsAsync<Domain.Validation.Exceptions.EntityInvalidException>(
async () => await handler.Handle(command, default));

Assert.Contains("Name is invalid", exception.Message);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the SalariesHistoricalDataRecordTemplate domain entity
fd "SalariesHistoricalDataRecordTemplate" --type cs | grep -v Tests

Repository: Techinterview-space/web-api

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Check the domain entity for Update and constructor methods
fd "SalariesHistoricalDataRecordTemplate\.cs" --type cs | grep -v Tests | head -1 | xargs cat -n

Repository: Techinterview-space/web-api

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Find Create handler and tests
fd "CreateSalariesHistoricalDataRecordTemplateHandler" --type cs | head -5

Repository: Techinterview-space/web-api

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Search for Create tests to see what exception they expect
rg "Handle.*Name.*ThrowsException" src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates --type cs -A 15 | head -80

Repository: Techinterview-space/web-api

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Find the SalariesHistoricalDataRecordTemplate domain entity (not in Tests)
fd "SalariesHistoricalDataRecordTemplate.cs" | grep -v Tests

Repository: Techinterview-space/web-api

Length of output: 148


🏁 Script executed:

#!/bin/bash
# Find Create handler and tests
fd "CreateSalariesHistoricalDataRecordTemplate" | head -10

Repository: Techinterview-space/web-api

Length of output: 554


🏁 Script executed:

#!/bin/bash
# Get directory structure to understand organization
find src -type d -name "*SalariesHistoricalDataTemplates*" | head -5

Repository: Techinterview-space/web-api

Length of output: 182


🏁 Script executed:

#!/bin/bash
# Examine the domain entity for Update and constructor methods
cat -n src/Domain/Entities/HistoricalRecords/SalariesHistoricalDataRecordTemplate.cs

Repository: Techinterview-space/web-api

Length of output: 1642


🏁 Script executed:

#!/bin/bash
# Examine the Create handler
cat -n src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateHandler.cs

Repository: Techinterview-space/web-api

Length of output: 2256


🏁 Script executed:

#!/bin/bash
# Search for Create tests to see what exception they expect for invalid name
rg "Handle.*Name.*Throws" src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/CreateSalariesHistoricalDataRecordTemplateHandlerTests.cs -A 20 | head -100

Repository: Techinterview-space/web-api

Length of output: 2558


🏁 Script executed:

#!/bin/bash
# Examine the Update handler
cat -n src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateHandler.cs

Repository: Techinterview-space/web-api

Length of output: 2689


🏁 Script executed:

#!/bin/bash
# Also check if there's a FluentValidation or similar validator
fd "UpdateSalariesHistoricalDataRecordTemplate.*Validator" | head -5

Repository: Techinterview-space/web-api

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Find the UpdateCommand
cat -n src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/UpdateSalariesHistoricalDataRecordTemplateCommand.cs

Repository: Techinterview-space/web-api

Length of output: 802


🏁 Script executed:

#!/bin/bash
# Search for ThrowIfInvalid implementation
rg "ThrowIfInvalid" --type cs -B 5 -A 10 | head -80

Repository: Techinterview-space/web-api

Length of output: 7792


🏁 Script executed:

#!/antml:thinking_end] bash
# Also check the base class or interface for request validation
fd "Validator" src/Web.Api/Features/SalariesHistoricalDataTemplates/UpdateTemplate/

Repository: Techinterview-space/web-api

Length of output: 53


🏁 Script executed:

#!/an/bash
# Find UpdateSalariesHistoricalDataRecordTemplateBodyRequest
cat -n src/Web.Api/Features/SalariesHistoricalDataTemplates/Shared/UpdateSalariesHistoricalDataRecordTemplateBodyRequest.cs

Repository: Techinterview-space/web-api

Length of output: 522


🏁 Script executed:

#!/an/bash
# Find CreateSalariesHistoricalDataRecordTemplateBodyRequest
cat -n src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateBodyRequest.cs

Repository: Techinterview-space/web-api

Length of output: 382


🏁 Script executed:

#!/bash
# Check if CreateCommand also inherits from validator or calls ThrowIfInvalid
rg "CreateSalariesHistoricalDataRecordTemplateCommand" src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateCommand.cs -A 20

Repository: Techinterview-space/web-api

Length of output: 419


🏁 Script executed:

#!/bash
# Verify that both commands inherit [Required] attributes
rg "\[Required\]" src/Web.Api/Features/SalariesHistoricalDataTemplates/Shared/UpdateSalariesHistoricalDataRecordTemplateBodyRequest.cs -B 1 -A 1

Repository: Techinterview-space/web-api

Length of output: 212


Align validation exception types between Create and Update operations.

The Create and Update operations use different validation strategies, resulting in inconsistent exception types for the same validation failure. Create throws BadRequestException (from the domain entity constructor), while Update throws EntityInvalidException (from request annotation validation via ThrowIfInvalid()). Both validate the same [Required] attribute on Name but at different layers.

To ensure consistent API behavior, either:

  • Add name validation to the Update() method in the domain entity (matching the constructor's approach) to throw BadRequestException
  • Apply ThrowIfInvalid() validation in the Create handler as well
  • Or define a single consistent validation strategy across both operations
🤖 Prompt for AI Agents
In
src/Web.Api.Tests/Features/SalariesHistoricalDataTemplates/UpdateSalariesHistoricalDataRecordTemplateHandlerTests.cs
around lines 64-159, the tests expect EntityInvalidException for
empty/null/whitespace Name while Create throws BadRequestException; align Update
with Create by adding the same Name validation to the domain entity's
Update(...) method so it throws BadRequestException on invalid Name. Change the
Update implementation to validate Name (non-null, non-empty, non-whitespace) and
throw the same BadRequestException used by the constructor, then update these
tests to Assert.ThrowsAsync<BadRequestException> (and assert the same message)
so Create/Update behavior is consistent.

Comment on lines +70 to +74
var queryParamsForDatabase = request.CreateDatabaseQueryParams();
var cacheKey = CacheKey + queryParamsForDatabase.GetKeyPostfix();
var surveyReplies = await _memoryCache
.GetOrCreateAsync(
CacheKey + request.GetKeyPostfix(),
cacheKey,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify GetKeyPostfix implementation includes all query filter dimensions

echo "=== Finding SalariesChartQueryParams related files ==="
fd "SalariesChartQueryParams" --type f -e cs

echo -e "\n=== Checking GetKeyPostfix implementations ==="
rg -n -B 2 -A 15 "GetKeyPostfix" --type cs

echo -e "\n=== Checking Skills and Cities filter usage in GetSurveyHistoricalChartHandler ==="
rg -n -B 2 -A 2 "Skills|Cities" "src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs"

Repository: Techinterview-space/web-api

Length of output: 9023


🏁 Script executed:

#!/bin/bash
# Verify the request/queryParamsForDatabase class structure

echo "=== Finding the request type for GetSurveyHistoricalChartHandler ==="
rg -n -B 5 -A 5 "Handle.*GetSurveyHistoricalChartRequest" "src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs"

echo -e "\n=== Checking GetSurveyHistoricalChartRequest class definition ==="
fd "GetSurveyHistoricalChartRequest" --type f -e cs

echo -e "\n=== Checking CreateDatabaseQueryParams method ==="
rg -n -B 2 -A 10 "CreateDatabaseQueryParams" --type cs

echo -e "\n=== Checking if there's a specialized GetKeyPostfix override ==="
rg -n -B 2 -A 15 "override.*GetKeyPostfix|class.*QueryParams.*{" --type cs | head -100

Repository: Techinterview-space/web-api

Length of output: 9475


🏁 Script executed:

#!/bin/bash
# Final verification: check if Skills/Cities are used in the database query

echo "=== Verify GetSurveyHistoricalChartHandler filters by Skills and Cities ==="
rg -n -B 1 -A 5 "request\.Skills|request\.Cities" "src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs"

echo -e "\n=== Check if HasValue validation includes Skills/Cities ==="
rg -n "HasValue|HasAnyFilter" "src/Domain/ValueObjects/SalariesChartQueryParamsBase.cs"

Repository: Techinterview-space/web-api

Length of output: 1951


Cache key must include Skills and Cities filters for correctness.

The cache key computed using GetKeyPostfix() only includes Grade and SelectedProfessionIds. However, the query also filters by Skills (line 97) and Cities (line 124), meaning different requests with the same Grade/Professions but different Skills or Cities will incorrectly share cached results. Update GetKeyPostfix() to include all filter dimensions used in the query.

🤖 Prompt for AI Agents
In
src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs
around lines 70 to 74, the cache key built from
request.CreateDatabaseQueryParams().GetKeyPostfix() omits Skills and Cities so
different requests can share the same cache entry; update GetKeyPostfix() to
include Skills and Cities (serialize selections deterministically, e.g., sort
IDs/strings, join with a clear delimiter, and handle null/empty collections
explicitly) so the cache key reflects all query filters used later in the
handler; ensure the new postfix format remains stable and URL-/cache-safe and
update any tests or consumers that assert the old key format.

Comment on lines +6 to +11
public CreateSalariesHistoricalDataRecordTemplateCommand(
CreateSalariesHistoricalDataRecordTemplateBodyRequest request)
{
Name = request.Name;
ProfessionIds = request.ProfessionIds;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove redundant constructor - properties are already inherited.

Since CreateSalariesHistoricalDataRecordTemplateCommand inherits from CreateSalariesHistoricalDataRecordTemplateBodyRequest, the Name and ProfessionIds properties are already available. The manual copying in the constructor is unnecessary and violates the DRY principle.

🔎 Simplify by removing the redundant constructor

If the command needs no additional behavior beyond the request body, consider eliminating this type entirely and using the body request directly. Otherwise, if additional command-specific properties or behavior are planned:

 public record CreateSalariesHistoricalDataRecordTemplateCommand
     : CreateSalariesHistoricalDataRecordTemplateBodyRequest
 {
-    public CreateSalariesHistoricalDataRecordTemplateCommand(
-        CreateSalariesHistoricalDataRecordTemplateBodyRequest request)
-    {
-        Name = request.Name;
-        ProfessionIds = request.ProfessionIds;
-    }
 }

Then construct instances using with-expressions or object initializers:

var command = request with { };  // Copy all properties from request

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

🤖 Prompt for AI Agents
In
src/Web.Api/Features/SalariesHistoricalDataTemplates/CreateTemplate/CreateSalariesHistoricalDataRecordTemplateCommand.cs
around lines 6–11, the constructor copies Name and ProfessionIds from the
inherited CreateSalariesHistoricalDataRecordTemplateBodyRequest which is
redundant; remove the entire constructor to rely on inherited properties (or, if
you need a distinct command type with no extra behavior, consider eliminating
the command type and use the request directly). After removing the constructor,
update any callers to either pass the request instance directly or create a
shallow copy via a with-expression/object initializer (e.g., use request with {
} or new Command { ... } where appropriate).

Comment on lines +11 to +12
[Required]
public List<long> ProfessionIds { get; init; } = new List<long>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

[Required] attribute ineffective on default-initialized collection.

The ProfessionIds property has both [Required] and a default value = new List<long>(). The [Required] attribute checks for null, but with the default initializer, the property will never be null, rendering the validation ineffective. If an empty request body is sent, ProfessionIds will default to an empty list and pass validation.

If you need to ensure the collection is provided and not empty, use [MinLength(1)] instead of or in addition to [Required], and remove the default initializer.

🔎 Proposed fix
 [Required]
+[MinLength(1)]
-public List<long> ProfessionIds { get; init; } = new List<long>();
+public List<long> ProfessionIds { get; init; }
🤖 Prompt for AI Agents
In
src/Web.Api/Features/SalariesHistoricalDataTemplates/Shared/UpdateSalariesHistoricalDataRecordTemplateBodyRequest.cs
around lines 11-12, the ProfessionIds property is default-initialized which
prevents the [Required] attribute from ever failing; remove the "= new
List<long>()" default initializer and add the [MinLength(1)] attribute to ensure
the collection is provided and not empty (you can optionally keep [Required]
alongside [MinLength(1)] for clarity).

Comment thread src/Web.Api/Web.Api.csproj Outdated
Comment on lines +11 to +14
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="10.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.0" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify NuGet package versions exist

echo "=== Checking Microsoft.AspNetCore.Authentication.JwtBearer 10.0.1 ==="
curl -s "https://api.nuget.org/v3-flatcontainer/microsoft.aspnetcore.authentication.jwtbearer/index.json" | jq -r '.versions[]' | grep "^10.0.1$" && echo "✓ Version 10.0.1 exists" || echo "✗ Version 10.0.1 NOT found"

echo ""
echo "=== Checking Swashbuckle.AspNetCore.SwaggerGen 10.1.0 ==="
curl -s "https://api.nuget.org/v3-flatcontainer/swashbuckle.aspnetcore.swaggergen/index.json" | jq -r '.versions[]' | grep "^10.1.0$" && echo "✓ Version 10.1.0 exists" || echo "✗ Version 10.1.0 NOT found"

echo ""
echo "=== Checking Swashbuckle.AspNetCore.SwaggerUI 10.1.0 ==="
curl -s "https://api.nuget.org/v3-flatcontainer/swashbuckle.aspnetcore.swaggerui/index.json" | jq -r '.versions[]' | grep "^10.1.0$" && echo "✓ Version 10.1.0 exists" || echo "✗ Version 10.1.0 NOT found"

Repository: Techinterview-space/web-api

Length of output: 350


🌐 Web query:

Microsoft.AspNetCore.Authentication.JwtBearer 10.0.1 security advisory CVE vulnerability

💡 Result:

Short answer: yes — Microsoft.AspNetCore.Authentication.JwtBearer used in some ASP.NET Core project templates was implicated in recent CVEs (a DoS CVE-2024-21319 affecting JWT-based templates and a critical HTTP request‑smuggling/feature‑bypass CVE-2025-55315 affecting ASP.NET Core/Kestrel). You should update affected packages to the patched versions or the latest servicing release and rebuild/redeploy. Sources and guidance:

  • CVE-2024-21319 (Denial of Service) — GitHub advisory for aspnetcore, mitigation: update Microsoft.AspNetCore.Authentication.JwtBearer / Microsoft.AspNetCore.Authentication.OpenIdConnect / Microsoft.IdentityModel.JsonWebTokens to latest. [1].
  • CVE-2025-55315 (HTTP request smuggling / security feature bypass) — Microsoft / GitHub advisories listing affected ASP.NET Core runtimes and patched releases; update to the October 2025 servicing updates or later. [2][3].
  • Microsoft .NET servicing posts summarizing October/December 2025 fixes and which releases include the patches. [4]

References:

  1. GitHub security advisory (CVE-2024-21319) — dotnet/aspnetcore security/advisories. [link].
  2. GitHub / Microsoft advisory for CVE-2025-55315 (ASP.NET Core Kestrel request smuggling) — advisories and patched package/runtime versions. [link].
  3. .NET Blog servicing updates (Oct/Dec 2025) listing these CVEs and the serviced releases. [link].

If you want, tell me the exact project/package version(s) you use (e.g., Microsoft.AspNetCore.Authentication.JwtBearer 10.0.1) and I’ll look up the specific patched target and update command for NuGet or your Docker images.


🌐 Web query:

Swashbuckle.AspNetCore 10.1.0 security advisory CVE vulnerability

💡 Result:

Short answer: There is no published CVE/security advisory for Swashbuckle.AspNetCore version 10.1.0. NuGet shows the 10.1.0 packages but no Swashbuckle CVE/advisory is listed. [1][2]

Context: the major recent .NET security advisory is CVE-2025-55315 (critical HTTP request/response smuggling) which affects ASP.NET Core / Kestrel runtimes — not Swashbuckle itself — and should be addressed by upgrading the ASP.NET Core runtime to the patched versions Microsoft published. [3][4]

Sources:

  • Swashbuckle.AspNetCore 10.1.0 (NuGet package listing). [1]
  • Swashbuckle.AspNetCore package pages (component package listings). [2]
  • Microsoft / dotnet advisory for CVE-2025-55315 (ASP.NET Core/Kestrel request-smuggling). [3]
  • News/technical writeup about the ASP.NET Core advisory and fixes. [4]

If you want, I can:

  • check your project for direct or transitive exposure to the ASP.NET Core runtime versions affected, or
  • fetch the exact patched runtime versions and upgrade guidance.

Update Microsoft.AspNetCore.Authentication.JwtBearer to a patched version.

Version 10.0.1 is exposed to CVE-2024-21319 (DoS) and CVE-2025-55315 (critical HTTP request smuggling/security feature bypass). Update to the October 2025 servicing release or later. Swashbuckle.AspNetCore packages (10.1.0) are free from known vulnerabilities.

🤖 Prompt for AI Agents
In src/Web.Api/Web.Api.csproj around lines 11 to 14, the
Microsoft.AspNetCore.Authentication.JwtBearer package is pinned to 10.0.1 which
is vulnerable; update the PackageReference for
Microsoft.AspNetCore.Authentication.JwtBearer to the October 2025 servicing
release (or any later patched version) in the csproj, run dotnet restore and
rebuild, update any lockfiles (global.json/nuget.config) if present, and run
tests and a dependency-scan to verify the vulnerable package is removed from the
dependency graph.

@maximgorbatyuk
maximgorbatyuk merged commit 3799dab into main Dec 26, 2025
3 checks passed
@maximgorbatyuk
maximgorbatyuk deleted the historical-data branch December 26, 2025 15:36
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.

1 participant