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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/Orbit.Api/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -561,12 +561,6 @@ public static WebApplicationBuilder AddOrbitObservability(this WebApplicationBui
{
builder.Services.Configure<SentrySettings>(
builder.Configuration.GetSection(SentrySettings.SectionName));
builder.Services.Configure<DiscordAlertSettings>(
builder.Configuration.GetSection(DiscordAlertSettings.SectionName));

var httpTimeout = TimeSpan.FromSeconds(builder.Configuration.GetValue("HttpClients:DefaultTimeoutSeconds", 30));
builder.Services.AddHttpClient("Discord", client => client.Timeout = httpTimeout);
builder.Services.AddSingleton<IAlertNotifier, DiscordAlertNotifier>();

var sentrySettings = builder.Configuration.GetSection(SentrySettings.SectionName).Get<SentrySettings>()
?? new SentrySettings();
Expand Down
17 changes: 1 addition & 16 deletions src/Orbit.Api/Middleware/UnhandledExceptionHandler.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using Microsoft.AspNetCore.Diagnostics;
using Orbit.Api.Extensions;
using Orbit.Domain.Interfaces;
using Sentry;

namespace Orbit.Api.Middleware;

internal sealed partial class UnhandledExceptionHandler(
ILogger<UnhandledExceptionHandler> logger,
IAlertNotifier alertNotifier) : IExceptionHandler
ILogger<UnhandledExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Expand All @@ -24,19 +22,6 @@ public async ValueTask<bool> TryHandleAsync(

SentrySdk.CaptureException(exception);

_ = alertNotifier.SendCriticalAsync(
$"{exception.GetType().Name}: {exception.Message}",
$"{method} {path}",
new Dictionary<string, string?>
{
["Method"] = method,
["Path"] = path,
["RequestId"] = requestId,
["ClientIp"] = clientIp,
["UserId"] = userId,
},
CancellationToken.None);

httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
httpContext.Response.ContentType = "application/json";
httpContext.Response.Headers[HttpContextExtensions.RequestIdHeaderName] = httpContext.GetRequestId();
Expand Down
3 changes: 0 additions & 3 deletions src/Orbit.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,5 @@
"Dsn": "",
"Environment": "production",
"TracesSampleRate": 0
},
"DiscordAlerts": {
"WebhookUrl": ""
}
}
16 changes: 0 additions & 16 deletions src/Orbit.Domain/Interfaces/IAlertNotifier.cs

This file was deleted.

This file was deleted.

107 changes: 0 additions & 107 deletions src/Orbit.Infrastructure/Services/DiscordAlertNotifier.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ await ProcessGoalDeadlineAsync(

private async Task<Dictionary<Guid, int>> ComputeFreshStreakValuesAsync(OrbitDbContext dbContext, CancellationToken ct)
{
var streakWindowStart = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(-AppConstants.MaxStreakLookbackDays);
var streakWindowStart = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(-AppConstants.MaxStreakLookbackDays - 1);
var streakGoals = await dbContext.Goals
.AsNoTracking()
.Where(g => g.Type == GoalType.Streak && g.Status == GoalStatus.Active && g.Deadline != null && !g.IsDeleted)
Expand Down
34 changes: 21 additions & 13 deletions src/Orbit.Infrastructure/Services/ReminderSchedulerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,31 +79,39 @@ private async Task ProcessRelativeReminders(OrbitDbContext dbContext, IPushNotif

var habitIds = habits.Select(h => h.Id).ToList();
var utcToday = DateOnly.FromDateTime(DateTime.UtcNow);
var loggedHabitIds = (await dbContext.HabitLogs
var minWindowDate = utcToday.AddDays(-1);
var maxWindowDate = utcToday.AddDays(1);

var loggedHabitDates = (await dbContext.HabitLogs
.AsNoTracking()
.Where(l => habitIds.Contains(l.HabitId) && l.Date == utcToday)
.Select(l => l.HabitId)
.ToListAsync(ct)).ToHashSet();
.Where(l => habitIds.Contains(l.HabitId)
&& l.Date >= minWindowDate && l.Date <= maxWindowDate)
.Select(l => new { l.HabitId, l.Date })
.ToListAsync(ct))
.Select(l => (l.HabitId, l.Date))
.ToHashSet();

var sentReminderKeys = await dbContext.SentReminders
.AsNoTracking()
.Where(r => habitIds.Contains(r.HabitId) && r.Date == utcToday && r.ReminderTimeUtc == null)
.Select(r => new { r.HabitId, r.MinutesBefore })
.Where(r => habitIds.Contains(r.HabitId) && r.ReminderTimeUtc == null
&& r.Date >= minWindowDate && r.Date <= maxWindowDate)
.Select(r => new { r.HabitId, r.Date, r.MinutesBefore })
.ToListAsync(ct);
var sentReminderSet = sentReminderKeys
.Select(r => (r.HabitId, r.MinutesBefore))
.Select(r => (r.HabitId, r.Date, r.MinutesBefore))
.ToHashSet();

foreach (var habit in habits)
{
await ProcessSingleRelativeReminderAsync(
habit, users, loggedHabitIds, sentReminderSet, pushService, dbContext, ct);
habit, users, loggedHabitDates, sentReminderSet, pushService, dbContext, ct);
}
}

private async Task ProcessSingleRelativeReminderAsync(
Habit habit, Dictionary<Guid, User> users, HashSet<Guid> loggedHabitIds,
HashSet<(Guid HabitId, int MinutesBefore)> sentReminderSet,
Habit habit, Dictionary<Guid, User> users,
HashSet<(Guid HabitId, DateOnly Date)> loggedHabitDates,
HashSet<(Guid HabitId, DateOnly Date, int MinutesBefore)> sentReminderSet,
IPushNotificationService pushService, OrbitDbContext dbContext, CancellationToken ct)
{
if (!users.TryGetValue(habit.UserId, out var user)) return;
Expand All @@ -114,21 +122,21 @@ private async Task ProcessSingleRelativeReminderAsync(
var userTimeNow = TimeOnly.FromDateTime(userNow);

if (!HabitScheduleService.IsHabitDueOnDate(habit, userToday)) return;
if (loggedHabitIds.Contains(habit.Id)) return;
if (loggedHabitDates.Contains((habit.Id, userToday))) return;

foreach (var minutesBefore in habit.ReminderTimes)
{
var reminderTime = habit.DueTime!.Value.AddMinutes(-minutesBefore);
if (userTimeNow < reminderTime) continue;
if (sentReminderSet.Contains((habit.Id, minutesBefore))) continue;
if (sentReminderSet.Contains((habit.Id, userToday, minutesBefore))) continue;

var lang = user.Language ?? "en";
var minutesText = FormatReminderText(minutesBefore, lang);

var sentReminder = SentReminder.Create(habit.Id, userToday, minutesBefore);
var notification = Notification.Create(habit.UserId, habit.Title, minutesText, "/", habit.Id);

sentReminderSet.Add((habit.Id, minutesBefore));
sentReminderSet.Add((habit.Id, userToday, minutesBefore));

if (!await TryRecordAndSendAsync(habit, sentReminder, notification, minutesText, pushService, dbContext, ct))
continue;
Expand Down
2 changes: 1 addition & 1 deletion src/Orbit.Infrastructure/Services/StreakGoalSyncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ internal async Task SyncActiveStreakGoals(CancellationToken ct)
using var scope = scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<OrbitDbContext>();

var streakWindowStart = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(-AppConstants.MaxStreakLookbackDays);
var streakWindowStart = DateOnly.FromDateTime(DateTime.UtcNow).AddDays(-AppConstants.MaxStreakLookbackDays - 1);
var goals = await dbContext.Goals
.Where(g => g.Type == GoalType.Streak && g.Status == GoalStatus.Active && !g.IsDeleted)
.Include(g => g.Habits).ThenInclude(h => h.Logs.Where(l => l.Date >= streakWindowStart))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using Orbit.Api.Extensions;
using Orbit.Api.Middleware;
using Orbit.Domain.Interfaces;

namespace Orbit.Infrastructure.Tests.Middleware;

Expand Down Expand Up @@ -58,8 +56,7 @@ public async Task ValidationExceptionHandler_ReturnsRequestIdInResponse()
public async Task UnhandledExceptionHandler_ReturnsStructured500WithRequestId()
{
var handler = new UnhandledExceptionHandler(
NullLogger<UnhandledExceptionHandler>.Instance,
Substitute.For<IAlertNotifier>());
NullLogger<UnhandledExceptionHandler>.Instance);
var httpContext = new DefaultHttpContext();
httpContext.TraceIdentifier = "req_server_123";
httpContext.Request.Method = HttpMethods.Post;
Expand Down

This file was deleted.

Loading
Loading