diff --git a/DysonNetwork.Sphere/Account/AccountController.cs b/DysonNetwork.Sphere/Account/AccountController.cs index 4e3ac0d..7d80774 100644 --- a/DysonNetwork.Sphere/Account/AccountController.cs +++ b/DysonNetwork.Sphere/Account/AccountController.cs @@ -257,7 +257,7 @@ public class AccountController( .OrderByDescending(s => s.CreatedAt) .FirstOrDefaultAsync(); if (status is null) return NotFound(); - + await events.ClearStatus(currentUser, status); return NoContent(); } @@ -268,31 +268,31 @@ public class AccountController( { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); var userId = currentUser.Id; - + var today = SystemClock.Instance.GetCurrentInstant().InUtc().Date; var localTime = new TimeOnly(0, 0); var startOfDay = today.ToDateOnly().ToDateTime(localTime).ToUniversalTime().ToInstant(); var endOfDay = today.PlusDays(1).ToDateOnly().ToDateTime(localTime).ToUniversalTime().ToInstant(); - + var result = await db.AccountCheckInResults .Where(x => x.AccountId == userId) .Where(x => x.CreatedAt >= startOfDay && x.CreatedAt < endOfDay) .OrderByDescending(x => x.CreatedAt) .FirstOrDefaultAsync(); - + return result is null ? NotFound() : Ok(result); } - + [HttpPost("me/check-in")] [Authorize] public async Task> DoCheckIn([FromBody] string? captchaToken) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - + var isAvailable = await events.CheckInDailyIsAvailable(currentUser); if (!isAvailable) return BadRequest("Check-in is not available for today."); - + var needsCaptcha = events.CheckInDailyDoAskCaptcha(currentUser); return needsCaptcha switch { @@ -303,6 +303,23 @@ public class AccountController( }; } + [HttpGet("me/calendar")] + [Authorize] + public async Task>> GetEventCalendar([FromQuery] int? month, [FromQuery] int? year) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + + var currentDate = SystemClock.Instance.GetCurrentInstant().InUtc().Date; + month ??= currentDate.Month; + year ??= currentDate.Year; + + if (month is < 1 or > 12) return BadRequest("Invalid month."); + if (year < 1) return BadRequest("Invalid year."); + + var calendar = await events.GetEventCalendar(currentUser, month.Value, year.Value); + return Ok(calendar); + } + [HttpGet("search")] public async Task> Search([FromQuery] string query, [FromQuery] int take = 20) { diff --git a/DysonNetwork.Sphere/Account/AccountEventService.cs b/DysonNetwork.Sphere/Account/AccountEventService.cs index def73bd..3cccaad 100644 --- a/DysonNetwork.Sphere/Account/AccountEventService.cs +++ b/DysonNetwork.Sphere/Account/AccountEventService.cs @@ -167,4 +167,45 @@ public class AccountEventService( return result; } + +public async Task> GetEventCalendar(Account user, int month, int year = 0) +{ + if (year == 0) + year = SystemClock.Instance.GetCurrentInstant().InUtc().Date.Year; + + // Create start and end dates for the specified month + var startOfMonth = new LocalDate(year, month, 1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); + var endOfMonth = startOfMonth.Plus(Duration.FromDays(DateTime.DaysInMonth(year, month))); + + var statuses = await db.AccountStatuses + .Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth) + .OrderBy(x => x.CreatedAt) + .ToListAsync(); + + var checkIn = await db.AccountCheckInResults + .Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth) + .ToListAsync(); + + var dates = Enumerable.Range(1, DateTime.DaysInMonth(year, month)) + .Select(day => new LocalDate(year, month, day).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant()) + .ToList(); + + var statusesByDate = statuses + .GroupBy(s => s.CreatedAt.InUtc().Date) + .ToDictionary(g => g.Key, g => g.ToList()); + + var checkInByDate = checkIn + .ToDictionary(c => c.CreatedAt.InUtc().Date); + + return dates.Select(date => + { + var utcDate = date.InUtc().Date; + return new DailyEventResponse + { + Date = date, + CheckInResult = checkInByDate.GetValueOrDefault(utcDate), + Statuses = statusesByDate.GetValueOrDefault(utcDate, new List()) + }; + }).ToList(); +} } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/Event.cs b/DysonNetwork.Sphere/Account/Event.cs index 24b077b..9031a59 100644 --- a/DysonNetwork.Sphere/Account/Event.cs +++ b/DysonNetwork.Sphere/Account/Event.cs @@ -49,4 +49,14 @@ public class FortuneTip public bool IsPositive { get; set; } public string Title { get; set; } = null!; public string Content { get; set; } = null!; +} + +/// +/// This method should not be mapped. Used to generate the daily event calendar. +/// +public class DailyEventResponse +{ + public Instant Date { get; set; } + public CheckInResult? CheckInResult { get; set; } + public ICollection Statuses { get; set; } = new List(); } \ No newline at end of file