Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dcbaac1
fix(coach): remove surface-level coach marks and rewrite unhelpful ones
ryceg Apr 24, 2026
8b5ccdd
feat(guest): add guest grant type, permission, and auth type constants
ryceg Apr 24, 2026
c4fb475
feat(guest): add guest link columns to oauth_grants
ryceg Apr 24, 2026
e07b7bd
feat(guest): add IGuestLinkService interface and implementation with …
ryceg Apr 24, 2026
9e974db
fix(guest): address spec review findings for GuestLinkService
ryceg Apr 24, 2026
2bb406b
feat(guest): add auth handler, API controller, and middleware support
ryceg Apr 24, 2026
3f0f85c
Merge main into feature/coach-mark-system
ryceg Apr 24, 2026
5e63ada
feat(guest): add guest entry page, session handling in hooks, and gue…
ryceg Apr 24, 2026
7299d85
fix(guest): remove unused httpError import from hooks
ryceg Apr 24, 2026
3beefa2
feat(guest): add guest link creation and management UI to members page
ryceg Apr 24, 2026
0294313
feat(guest): add rate limiting for guest link activation (5 per IP pe…
ryceg Apr 24, 2026
007c204
feat(entries): add EntryProjection static mappers for V4-to-legacy En…
ryceg Apr 24, 2026
5777cd5
feat(entries): add EntryReadService to replace DualPathEntryStore wit…
ryceg Apr 24, 2026
f0e3192
fix(entries): exclude demo data, push pagination to DB, document Date…
ryceg Apr 24, 2026
f3dbb2c
feat(entries): route entry writes through V4 decomposer instead of le…
ryceg Apr 24, 2026
da11fc7
feat(entries): wire EntryReadService, update V3 controller to use ser…
ryceg Apr 24, 2026
08d67d6
refactor: remove DualPathEntryStore and legacy dual-path domain logic
ryceg Apr 24, 2026
b8dac91
fix(tests): seed V4 entities in golden file tests instead of legacy e…
ryceg Apr 24, 2026
c94dc1f
refactor: migrate simple entry reads from IEntryRepository to IEntryS…
ryceg Apr 24, 2026
57b87c6
refactor: migrate data source operations from IEntryRepository to V4 …
ryceg Apr 24, 2026
a260c8d
refactor: migrate demo entry service from IEntryRepository to V4 repo…
ryceg Apr 24, 2026
d469961
Merge main into feature/v1-deprecation
ryceg Apr 24, 2026
5e182f1
refactor: migrate RetrospectiveController from IEntryRepository to IE…
ryceg Apr 25, 2026
78f4be2
feat(entries): add CountAsync to IEntryStore backed by V4 repositorie…
ryceg Apr 25, 2026
8a4ec6e
refactor: migrate CountController from IEntryRepository to IEntryStor…
ryceg Apr 25, 2026
df1db35
refactor: migrate TimeQueryService from IEntryRepository to IEntrySer…
ryceg Apr 25, 2026
3d3894e
feat(v4): add DeleteByTimeRangeAsync to glucose repository interfaces…
ryceg Apr 25, 2026
5bff90f
feat(entries): add BulkDeleteAsync to IEntryDecomposer for V4 bulk de…
ryceg Apr 25, 2026
ddec568
refactor: remove IEntryRepository from EntryService, use IEntryDecomp…
ryceg Apr 25, 2026
fa75c86
refactor: migrate health endpoint from IEntryRepository to IEntryStor…
ryceg Apr 25, 2026
85afdbe
refactor: remove legacy Entries queries from DataOverviewService (Pha…
ryceg Apr 25, 2026
5b97b73
refactor: migrate DevAdminController from legacy Entries to V4 tables…
ryceg Apr 25, 2026
10e4b70
refactor: remove EntryEntity dependency from DeduplicationService (Ph…
ryceg Apr 25, 2026
9009364
refactor: delete IEntryRepository, EntryRepository, EntryEntity, Entr…
ryceg Apr 25, 2026
35f41d3
feat(db): add DropEntriesTable migration (Phase 6i)
ryceg Apr 25, 2026
5836c6c
fix: add $gt/$lt support to ParseTimeRangeFromFind, fix DeduplicateEn…
ryceg Apr 25, 2026
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
15 changes: 8 additions & 7 deletions src/API/Nocturne.API/Controllers/V1/CountController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Nocturne.API.Attributes;
using Nocturne.Core.Contracts.Entries;
using Nocturne.Core.Contracts.Repositories;

namespace Nocturne.API.Controllers.V1;
Expand All @@ -8,7 +9,7 @@ namespace Nocturne.API.Controllers.V1;
/// Count controller that provides 1:1 compatibility with Nightscout count endpoints.
/// Implements the /api/v1/count/* endpoints from the legacy JavaScript implementation.
/// </summary>
/// <seealso cref="IEntryRepository"/>
/// <seealso cref="IEntryStore"/>
/// <seealso cref="ITreatmentRepository"/>
/// <seealso cref="IDeviceStatusRepository"/>
/// <seealso cref="IProfileRepository"/>
Expand All @@ -18,7 +19,7 @@ namespace Nocturne.API.Controllers.V1;
[Route("api/v1/[controller]")]
public class CountController : ControllerBase
{
private readonly IEntryRepository _entryRepository;
private readonly IEntryStore _entryStore;
private readonly ITreatmentRepository _treatmentRepository;
private readonly IDeviceStatusRepository _deviceStatusRepository;
private readonly IProfileRepository _profileRepository;
Expand All @@ -29,15 +30,15 @@ public class CountController : ControllerBase
/// <summary>
/// Initializes a new instance of <see cref="CountController"/>.
/// </summary>
/// <param name="entryRepository">Repository for glucose entry records.</param>
/// <param name="entryStore">Store for glucose entry records.</param>
/// <param name="treatmentRepository">Repository for treatment records.</param>
/// <param name="deviceStatusRepository">Repository for device status records.</param>
/// <param name="profileRepository">Repository for profile records.</param>
/// <param name="foodRepository">Repository for food records.</param>
/// <param name="activityRepository">Repository for activity records.</param>
/// <param name="logger">Logger instance.</param>
public CountController(
IEntryRepository entryRepository,
IEntryStore entryStore,
ITreatmentRepository treatmentRepository,
IDeviceStatusRepository deviceStatusRepository,
IProfileRepository profileRepository,
Expand All @@ -46,7 +47,7 @@ public CountController(
ILogger<CountController> logger
)
{
_entryRepository = entryRepository;
_entryStore = entryStore;
_treatmentRepository = treatmentRepository;
_deviceStatusRepository = deviceStatusRepository;
_profileRepository = profileRepository;
Expand Down Expand Up @@ -80,7 +81,7 @@ public async Task<ActionResult<CountResponse>> CountEntries(

try
{
var count = await _entryRepository.CountEntriesAsync(find, type, cancellationToken);
var count = await _entryStore.CountAsync(find, type, cancellationToken);

_logger.LogDebug("Found {Count} entries matching criteria", count);
return Ok(new CountResponse { Count = count });
Expand Down Expand Up @@ -287,7 +288,7 @@ public async Task<ActionResult<CountResponse>> CountGeneric(
switch (storage.ToLowerInvariant())
{
case "entries":
count = await _entryRepository.CountEntriesAsync(
count = await _entryStore.CountAsync(
find,
type,
cancellationToken
Expand Down
23 changes: 9 additions & 14 deletions src/API/Nocturne.API/Controllers/V1/DebugController.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
using Microsoft.AspNetCore.Mvc;
using Nocturne.Core.Contracts.Entries;
using Nocturne.Core.Models;
using Nocturne.Core.Contracts.Repositories;

namespace Nocturne.API.Controllers.V1;

/// <summary>
/// V1 debug controller for development and diagnostics.
/// Provides basic database connectivity tests against the PostgreSQL data store.
/// </summary>
/// <seealso cref="IEntryRepository"/>
/// <seealso cref="IEntryStore"/>
[ApiController]
[Route("api/v1/[controller]")]
public class DebugController : ControllerBase
{
private readonly IEntryRepository _entryRepository;
private readonly IEntryStore _store;
private readonly ILogger<DebugController> _logger;

public DebugController(IEntryRepository entryRepository, ILogger<DebugController> logger)
public DebugController(IEntryStore store, ILogger<DebugController> logger)
{
_entryRepository = entryRepository;
_store = store;
_logger = logger;
}

Expand All @@ -29,20 +29,15 @@ public async Task<IActionResult> TestPostgreSqlConnection()
{
_logger.LogInformation("Testing PostgreSQL connection");

// Count documents
var count = await _entryRepository.CountEntriesAsync();
_logger.LogInformation("Entries table document count: {Count}", count);

// Try to get recent entries
var entries = await _entryRepository.GetEntriesAsync("sgv", 1, 0);
// Try to get recent entries as a connectivity check
var entries = await _store.QueryAsync(new EntryQuery { Type = "sgv", Count = 1 });
var firstEntry = entries.FirstOrDefault();

return Ok(
new
{
DatabaseType = "PostgreSQL",
TableName = "entries",
DocumentCount = count,
HasEntries = firstEntry != null,
SampleEntry = firstEntry,
Status = "Success",
}
Expand All @@ -68,7 +63,7 @@ public async Task<IActionResult> GetEntriesDirect()
{
try
{
var entries = await _entryRepository.GetEntriesAsync("sgv", 5, 0);
var entries = await _store.QueryAsync(new EntryQuery { Type = "sgv", Count = 5 });
return Ok(entries);
}
catch (Exception ex)
Expand Down
49 changes: 15 additions & 34 deletions src/API/Nocturne.API/Controllers/V3/EntriesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@
using Nocturne.Core.Contracts.Alerts;
using Nocturne.Core.Models;
using Nocturne.Core.Models.Extensions;
using Nocturne.Core.Contracts.Repositories;

namespace Nocturne.API.Controllers.V3;

/// <summary>
/// V3 Entries controller that provides full V3 API compatibility with Nightscout entries endpoints.
/// Implements the /api/v3/entries endpoints with pagination, field selection, sorting, and advanced filtering.
/// </summary>
/// <seealso cref="IEntryService"/>
/// <seealso cref="IEntryRepository"/>
/// <seealso cref="IAlertOrchestrator"/>
/// <seealso cref="Entry"/>
/// <seealso cref="BaseV3Controller{T}"/>
Expand All @@ -27,20 +24,17 @@ namespace Nocturne.API.Controllers.V3;
[Authorize(Policy = PolicyNames.HasPermissions)]
public class EntriesController : BaseV3Controller<Entry>
{
private readonly IEntryRepository _entries;
private readonly IEntryService _entryService;
private readonly IAlertOrchestrator _alertOrchestrator;

public EntriesController(
IEntryRepository entries,
IDocumentProcessingService documentProcessingService,
IEntryService entryService,
IAlertOrchestrator alertOrchestrator,
ILogger<EntriesController> logger
)
: base(documentProcessingService, logger)
{
_entries = entries;
_entryService = entryService;
_alertOrchestrator = alertOrchestrator;
}
Expand Down Expand Up @@ -94,8 +88,8 @@ public async Task<ActionResult> GetEntries(CancellationToken cancellationToken =
var hasSortDesc = HttpContext?.Request.Query.ContainsKey("sort$desc") ?? false;
var reverseResults = !hasSortDesc && ExtractSortDirection(parameters.Sort);

// Get entries using existing V1 backend with V3 parameters
var entries = await _entries.GetEntriesWithAdvancedFilterAsync(
// Get entries using service layer with V3 parameters
var entries = await _entryService.GetEntriesWithAdvancedFilterAsync(
type: type,
count: parameters.Limit,
skip: parameters.Offset,
Expand Down Expand Up @@ -159,7 +153,7 @@ public async Task<ActionResult<Entry>> GetEntry(

try
{
var entry = await _entries.GetEntryByIdAsync(id, cancellationToken);
var entry = await _entryService.GetEntryByIdAsync(id, cancellationToken);

if (entry == null)
{
Expand Down Expand Up @@ -406,7 +400,7 @@ public async Task<ActionResult<Entry>> UpdateEntry(
var processedEntry = _documentProcessingService.ProcessEntry(entry);

// Update in database
var updatedEntry = await _entries.UpdateEntryAsync(
var updatedEntry = await _entryService.UpdateEntryAsync(
id,
processedEntry,
cancellationToken
Expand Down Expand Up @@ -458,7 +452,7 @@ public async Task<ActionResult> DeleteEntry(

try
{
var deleted = await _entries.DeleteEntryAsync(id, cancellationToken);
var deleted = await _entryService.DeleteEntryAsync(id, cancellationToken);

if (!deleted)
{
Expand Down Expand Up @@ -509,10 +503,16 @@ public async Task<ActionResult> GetEntryHistory(
{
limit = Math.Min(Math.Max(limit, 1), 1000);

var entries = await _entries.GetEntriesModifiedSinceAsync(
lastModified,
limit,
cancellationToken
// Build a find query for entries since the given timestamp
var findQuery = $"{{\"date\":{{\"$gte\":{lastModified}}}}}";
var entries = await _entryService.GetEntriesWithAdvancedFilterAsync(
type: null,
count: limit,
skip: 0,
findQuery: findQuery,
dateString: null,
reverseResults: false,
cancellationToken: cancellationToken
);
var v3Entries = entries.ToV3Responses().ToList();
return CreateV3SuccessResponse(v3Entries);
Expand Down Expand Up @@ -637,25 +637,6 @@ public async Task<ActionResult> GetEntryHistory(
return JsonSerializer.Serialize(conditions);
}

private async Task<long> GetTotalCountAsync(
string? type,
string? findQuery,
CancellationToken cancellationToken
)
{
try
{
// Use the count endpoint to get total
return await _entries.CountEntriesAsync(findQuery, type, cancellationToken);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get total count for V3 entries, using estimate");
// Return a reasonable estimate if count fails
return 1000;
}
}

private DateTimeOffset GetLastModified(List<Entry> entries)
{
if (entries.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Nocturne.Core.Contracts.Treatments;
using Nocturne.Core.Contracts.Glucose;
using Nocturne.Core.Models;
using Nocturne.Core.Contracts.Repositories;
namespace Nocturne.API.Controllers.V4.Analytics;
/// <summary>
/// Controller providing retrospective (day-in-review) diabetes data.
Expand Down Expand Up @@ -40,7 +39,6 @@ public class RetrospectiveController : ControllerBase
private readonly ITreatmentService _treatmentService;
private readonly IDeviceStatusService _deviceStatusService;
private readonly IProfileService _profileService;
private readonly IEntryRepository _entryRepository;
private readonly ILogger<RetrospectiveController> _logger;
public RetrospectiveController(
IIobService iobService,
Expand All @@ -49,7 +47,6 @@ public RetrospectiveController(
ITreatmentService treatmentService,
IDeviceStatusService deviceStatusService,
IProfileService profileService,
IEntryRepository entryRepository,
ILogger<RetrospectiveController> logger
)
{
Expand All @@ -59,7 +56,6 @@ ILogger<RetrospectiveController> logger
_treatmentService = treatmentService;
_deviceStatusService = deviceStatusService;
_profileService = profileService;
_entryRepository = entryRepository;
_logger = logger;
}
/// <summary>
Expand Down Expand Up @@ -93,11 +89,13 @@ public async Task<ActionResult<RetrospectiveDataResponse>> GetRetrospectiveData(
var fromMills = time - (30 * 60 * 1000); // 30 minutes before
var toMills = time + (5 * 60 * 1000); // 5 minutes after
var findQuery = $"{{\"mills\":{{\"$gte\":{fromMills},\"$lte\":{toMills}}}}}";
var entries = await _entryRepository.GetEntriesWithAdvancedFilterAsync(
var entries = await _entryService.GetEntriesWithAdvancedFilterAsync(
type: "sgv",
count: 50,
skip: 0,
findQuery: findQuery,
dateString: null,
reverseResults: false,
cancellationToken: cancellationToken
);
var entryList = entries?.ToList() ?? new List<Entry>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,9 @@ public async Task<ActionResult<List<DevTenantSummaryDto>>> ListTenants(Cancellat
{
await SetTenantGuc(tenant.Id, ct);

var entryCount = await _db.Entries.LongCountAsync(ct);
var entryCount = (long)await _db.SensorGlucose.CountAsync(ct)
+ await _db.MeterGlucose.CountAsync(ct)
+ await _db.Calibrations.CountAsync(ct);
var treatmentCount = await _db.Treatments.LongCountAsync(ct);
var deviceStatusCount = await _db.DeviceStatuses.LongCountAsync(ct);
var profileCount = await _db.Profiles.CountAsync(ct);
Expand All @@ -673,9 +675,9 @@ public async Task<ActionResult<List<DevTenantSummaryDto>>> ListTenants(Cancellat
c.LastErrorMessage))
.ToListAsync(ct);

var latestEntry = await _db.Entries
.OrderByDescending(e => e.SysCreatedAt)
.Select(e => (DateTime?)e.SysCreatedAt)
var latestEntry = await _db.SensorGlucose
.OrderByDescending(e => e.Timestamp)
.Select(e => (DateTime?)e.Timestamp)
.FirstOrDefaultAsync(ct);

summaries.Add(new DevTenantSummaryDto(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using Nocturne.API.Models;
using Nocturne.API.Services.Connectors;
using Nocturne.Core.Contracts.Connectors;
using Nocturne.Core.Models.Services;
using Nocturne.Core.Contracts.Repositories;
using Nocturne.Core.Models.Services;

namespace Nocturne.API.Controllers.V4.Platform;

Expand All @@ -22,7 +22,6 @@ namespace Nocturne.API.Controllers.V4.Platform;
public class ServicesController : ControllerBase
{
private readonly IDataSourceService _dataSourceService;
private readonly IEntryRepository _entryRepository;
private readonly ITreatmentRepository _treatmentRepository;
private readonly IConnectorHealthService _connectorHealthService;
private readonly IConnectorSyncService _connectorSyncService;
Expand All @@ -33,15 +32,13 @@ public class ServicesController : ControllerBase
/// Initializes a new instance of <see cref="ServicesController"/>.
/// </summary>
/// <param name="dataSourceService">Service for querying active data sources and their status.</param>
/// <param name="entryRepository">Repository used to compute last-entry timestamps.</param>
/// <param name="treatmentRepository">Repository used to compute last-treatment timestamps.</param>
/// <param name="connectorHealthService">Service for connector health state queries.</param>
/// <param name="connectorSyncService">Service for triggering on-demand connector syncs.</param>
/// <param name="logger">Logger instance.</param>
/// <param name="configuration">Application configuration for base URL resolution.</param>
public ServicesController(
IDataSourceService dataSourceService,
IEntryRepository entryRepository,
ITreatmentRepository treatmentRepository,
IConnectorHealthService connectorHealthService,
IConnectorSyncService connectorSyncService,
Expand All @@ -50,7 +47,6 @@ IConfiguration configuration
)
{
_dataSourceService = dataSourceService;
_entryRepository = entryRepository;
_treatmentRepository = treatmentRepository;
_connectorHealthService = connectorHealthService;
_connectorSyncService = connectorSyncService;
Expand Down Expand Up @@ -473,14 +469,14 @@ public async Task<ActionResult<ConnectorSyncStatus>> GetConnectorSyncStatus(
// Map connector ID to data source name used in database
var dataSource = MapConnectorIdToDataSource(id);

// Get latest timestamps from database
var entryTimestamp = await _entryRepository.GetLatestEntryTimestampBySourceAsync(
// Get latest timestamps from V4 glucose tables
var entryTimestamp = await _dataSourceService.GetLatestGlucoseTimestampBySourceAsync(
dataSource,
cancellationToken
);

var oldestEntryTimestamp =
await _entryRepository.GetOldestEntryTimestampBySourceAsync(
await _dataSourceService.GetOldestGlucoseTimestampBySourceAsync(
dataSource,
cancellationToken
);
Expand Down
Loading
Loading