Notable days (holiday)

This commit is contained in:
2025-09-07 14:33:24 +08:00
parent 5f324a2348
commit 9492b6cac6
5 changed files with 141 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
using Nager.Holiday;
using NodaTime;
namespace DysonNetwork.Pass.Account;
/// <summary>
/// Reference from Nager.Holiday
/// </summary>
public enum NotableHolidayType
{
/// <summary>Public holiday</summary>
Public,
/// <summary>Bank holiday, banks and offices are closed</summary>
Bank,
/// <summary>School holiday, schools are closed</summary>
School,
/// <summary>Authorities are closed</summary>
Authorities,
/// <summary>Majority of people take a day off</summary>
Optional,
/// <summary>Optional festivity, no paid day off</summary>
Observance,
}
public class NotableDay
{
public Instant Date { get; set; }
public string? LocalName { get; set; }
public string? GlobalName { get; set; }
public string? CountryCode { get; set; }
public NotableHolidayType[] Holidays { get; set; } = [];
public static NotableDay FromNagerHoliday(PublicHoliday holiday)
{
return new NotableDay()
{
Date = Instant.FromDateTimeUtc(holiday.Date.ToUniversalTime()),
LocalName = holiday.LocalName,
GlobalName = holiday.Name,
CountryCode = holiday.CountryCode,
Holidays = holiday.Types?.Select(x => x switch
{
PublicHolidayType.Public => NotableHolidayType.Public,
PublicHolidayType.Bank => NotableHolidayType.Bank,
PublicHolidayType.School => NotableHolidayType.School,
PublicHolidayType.Authorities => NotableHolidayType.Authorities,
PublicHolidayType.Optional => NotableHolidayType.Optional,
_ => NotableHolidayType.Observance
}).ToArray() ?? [],
};
}
}

View File

@@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace DysonNetwork.Pass.Account;
[ApiController]
[Route("/api/notable")]
public class NotableDaysController(NotableDaysService days) : ControllerBase
{
[HttpGet("{regionCode}/{year:int}")]
public async Task<ActionResult<List<NotableDay>>> GetRegionDays(string regionCode, int year)
{
var result = await days.GetNotableDays(year, regionCode);
return Ok(result);
}
[HttpGet("{regionCode}")]
public async Task<ActionResult<List<NotableDay>>> GetRegionDaysCurrentYear(string regionCode)
{
var currentYear = DateTime.Now.Year;
var result = await days.GetNotableDays(currentYear, regionCode);
return Ok(result);
}
[HttpGet("me/{year:int}")]
[Authorize]
public async Task<ActionResult<List<NotableDay>>> GetAccountNotableDays(int year)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var region = currentUser.Region;
if (string.IsNullOrWhiteSpace(region)) region = "us";
var result = await days.GetNotableDays(year, region);
return Ok(result);
}
[HttpGet("me")]
[Authorize]
public async Task<ActionResult<List<NotableDay>>> GetAccountNotableDaysCurrentYear()
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var currentYear = DateTime.Now.Year;
var region = currentUser.Region;
if (string.IsNullOrWhiteSpace(region)) region = "us";
var result = await days.GetNotableDays(currentYear, region);
return Ok(result);
}
}

View File

@@ -0,0 +1,34 @@
using DysonNetwork.Shared.Cache;
using Nager.Holiday;
namespace DysonNetwork.Pass.Account;
public class NotableDaysService(ICacheService cache)
{
private const string NotableDaysCacheKeyPrefix = "notable:";
public async Task<List<NotableDay>> GetNotableDays(int? year, string regionCode)
{
year ??= DateTime.UtcNow.Year;
// Generate cache key using year and region code
var cacheKey = $"{NotableDaysCacheKeyPrefix}:{year}:{regionCode}";
// Try to get from cache first
var (found, cachedDays) = await cache.GetAsyncWithStatus<List<NotableDay>>(cacheKey);
if (found && cachedDays != null)
{
return cachedDays;
}
// If not in cache, fetch from API
using var holidayClient = new HolidayClient();
var holidays = await holidayClient.GetHolidaysAsync(year.Value, regionCode);
var days = holidays?.Select(NotableDay.FromNagerHoliday).ToList() ?? [];
// Cache the result for 1 day (holiday data doesn't change frequently)
await cache.SetAsync(cacheKey, days, TimeSpan.FromDays(1));
return days;
}
}

View File

@@ -193,6 +193,7 @@ public static class ServiceCollectionExtensions
services.AddScoped<ActionLogService>();
services.AddScoped<AccountService>();
services.AddScoped<AccountEventService>();
services.AddScoped<NotableDaysService>();
services.AddScoped<ActionLogService>();
services.AddScoped<RelationshipService>();
services.AddScoped<MagicSpellService>();