🐛 Fix bugs
This commit is contained in:
		| @@ -224,6 +224,44 @@ public class AccountController( | ||||
|         return Ok(status); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("{name}/statuses")] | ||||
|     public async Task<ActionResult<Status>> GetOtherStatus(string name) | ||||
|     { | ||||
|         var account = await db.Accounts.FirstOrDefaultAsync(a => a.Name == name); | ||||
|         if (account is null) return BadRequest(); | ||||
|         var status = await events.GetStatus(account.Id); | ||||
|         status.IsInvisible = false; // Keep the invisible field not available for other users | ||||
|         return Ok(status); | ||||
|     } | ||||
|  | ||||
|     [HttpPatch("me/statuses")] | ||||
|     [Authorize] | ||||
|     [RequiredPermission("global", "accounts.statuses.update")] | ||||
|     public async Task<ActionResult<Status>> UpdateStatus([FromBody] StatusRequest request) | ||||
|     { | ||||
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); | ||||
|  | ||||
|         var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|         var status = await db.AccountStatuses | ||||
|             .Where(e => e.AccountId == currentUser.Id) | ||||
|             .Where(e => e.ClearedAt == null || e.ClearedAt > now) | ||||
|             .OrderByDescending(e => e.CreatedAt) | ||||
|             .FirstOrDefaultAsync(); | ||||
|         if (status is null) return NotFound(); | ||||
|          | ||||
|         status.Attitude = request.Attitude; | ||||
|         status.IsInvisible = request.IsInvisible; | ||||
|         status.IsNotDisturb = request.IsNotDisturb; | ||||
|         status.Label = request.Label; | ||||
|         status.ClearedAt = request.ClearedAt; | ||||
|          | ||||
|         db.Update(status); | ||||
|         await db.SaveChangesAsync(); | ||||
|         events.PurgeStatusCache(currentUser.Id); | ||||
|          | ||||
|         return status; | ||||
|     } | ||||
|  | ||||
|     [HttpPost("me/statuses")] | ||||
|     [Authorize] | ||||
|     [RequiredPermission("global", "accounts.statuses.create")] | ||||
| @@ -305,7 +343,8 @@ public class AccountController( | ||||
|  | ||||
|     [HttpGet("me/calendar")] | ||||
|     [Authorize] | ||||
|     public async Task<ActionResult<List<DailyEventResponse>>> GetEventCalendar([FromQuery] int? month, [FromQuery] int? year) | ||||
|     public async Task<ActionResult<List<DailyEventResponse>>> GetEventCalendar([FromQuery] int? month, | ||||
|         [FromQuery] int? year) | ||||
|     { | ||||
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); | ||||
|  | ||||
| @@ -320,6 +359,27 @@ public class AccountController( | ||||
|         return Ok(calendar); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("{name}/calendar")] | ||||
|     public async Task<ActionResult<List<DailyEventResponse>>> GetOtherEventCalendar( | ||||
|         string name, | ||||
|         [FromQuery] int? month, | ||||
|         [FromQuery] int? year | ||||
|     ) | ||||
|     { | ||||
|         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 account = await db.Accounts.FirstOrDefaultAsync(a => a.Name == name); | ||||
|         if (account is null) return BadRequest(); | ||||
|  | ||||
|         var calendar = await events.GetEventCalendar(account, month.Value, year.Value, replaceInvisible: true); | ||||
|         return Ok(calendar); | ||||
|     } | ||||
|  | ||||
|     [HttpGet("search")] | ||||
|     public async Task<List<Account>> Search([FromQuery] string query, [FromQuery] int take = 20) | ||||
|     { | ||||
|   | ||||
| @@ -1,12 +1,10 @@ | ||||
| using System.Globalization; | ||||
| using DysonNetwork.Sphere.Activity; | ||||
| using DysonNetwork.Sphere.Connection; | ||||
| using DysonNetwork.Sphere.Resources; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using Microsoft.Extensions.Caching.Memory; | ||||
| using Microsoft.Extensions.Localization; | ||||
| using NodaTime; | ||||
| using NodaTime; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Account; | ||||
|  | ||||
| @@ -21,11 +19,20 @@ public class AccountEventService( | ||||
|     private static readonly Random Random = new(); | ||||
|     private const string StatusCacheKey = "account_status_"; | ||||
|  | ||||
|     public void PurgeStatusCache(long userId) | ||||
|     { | ||||
|         var cacheKey = $"{StatusCacheKey}{userId}"; | ||||
|         cache.Remove(cacheKey); | ||||
|     } | ||||
|  | ||||
|     public async Task<Status> GetStatus(long userId) | ||||
|     { | ||||
|         var cacheKey = $"{StatusCacheKey}{userId}"; | ||||
|         if (cache.TryGetValue(cacheKey, out Status? cachedStatus)) | ||||
|             return cachedStatus!; | ||||
|         { | ||||
|             cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); | ||||
|             return cachedStatus; | ||||
|         } | ||||
|  | ||||
|         var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|         var status = await db.AccountStatuses | ||||
| @@ -33,19 +40,21 @@ public class AccountEventService( | ||||
|             .Where(e => e.ClearedAt == null || e.ClearedAt > now) | ||||
|             .OrderByDescending(e => e.CreatedAt) | ||||
|             .FirstOrDefaultAsync(); | ||||
|         var isOnline = ws.GetAccountIsConnected(userId); | ||||
|         if (status is not null) | ||||
|         { | ||||
|             status.IsOnline = !status.IsInvisible && isOnline; | ||||
|             cache.Set(cacheKey, status, TimeSpan.FromMinutes(5)); | ||||
|             return status; | ||||
|         } | ||||
|  | ||||
|         var isOnline = ws.GetAccountIsConnected(userId); | ||||
|         if (isOnline) | ||||
|         { | ||||
|             return new Status | ||||
|             { | ||||
|                 Attitude = StatusAttitude.Neutral, | ||||
|                 IsOnline = true, | ||||
|                 IsCustomized = false, | ||||
|                 Label = "Online", | ||||
|                 AccountId = userId, | ||||
|             }; | ||||
| @@ -55,6 +64,7 @@ public class AccountEventService( | ||||
|         { | ||||
|             Attitude = StatusAttitude.Neutral, | ||||
|             IsOnline = false, | ||||
|             IsCustomized = false, | ||||
|             Label = "Offline", | ||||
|             AccountId = userId, | ||||
|         }; | ||||
| @@ -85,6 +95,7 @@ public class AccountEventService( | ||||
|         status.ClearedAt = SystemClock.Instance.GetCurrentInstant(); | ||||
|         db.Update(status); | ||||
|         await db.SaveChangesAsync(); | ||||
|         PurgeStatusCache(user.Id); | ||||
|     } | ||||
|  | ||||
|     private const int FortuneTipCount = 7; // This will be the max index for each type (positive/negative) | ||||
| @@ -123,8 +134,8 @@ public class AccountEventService( | ||||
|     { | ||||
|         var cultureInfo = new CultureInfo(user.Language, false); | ||||
|         CultureInfo.CurrentCulture = cultureInfo; | ||||
|         CultureInfo.CurrentUICulture = cultureInfo;        | ||||
|          | ||||
|         CultureInfo.CurrentUICulture = cultureInfo; | ||||
|  | ||||
|         // Generate 2 positive tips | ||||
|         var positiveIndices = Enumerable.Range(1, FortuneTipCount) | ||||
|             .OrderBy(_ => Random.Next()) | ||||
| @@ -168,44 +179,60 @@ public class AccountEventService( | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
| public async Task<List<DailyEventResponse>> 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 => | ||||
|     public async Task<List<DailyEventResponse>> GetEventCalendar(Account user, int month, int year = 0, | ||||
|         bool replaceInvisible = false) | ||||
|     { | ||||
|         var utcDate = date.InUtc().Date; | ||||
|         return new DailyEventResponse | ||||
|         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 | ||||
|             .AsNoTracking() | ||||
|             .TagWith("GetEventCalendar_Statuses") | ||||
|             .Where(x => x.AccountId == user.Id && x.CreatedAt >= startOfMonth && x.CreatedAt < endOfMonth) | ||||
|             .Select(x => new Status | ||||
|             { | ||||
|                 Id = x.Id, | ||||
|                 Attitude = x.Attitude, | ||||
|                 IsInvisible = !replaceInvisible && x.IsInvisible, | ||||
|                 IsNotDisturb = x.IsNotDisturb, | ||||
|                 Label = x.Label, | ||||
|                 ClearedAt = x.ClearedAt, | ||||
|                 AccountId = x.AccountId, | ||||
|                 CreatedAt = x.CreatedAt | ||||
|             }) | ||||
|             .OrderBy(x => x.CreatedAt) | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         var checkIn = await db.AccountCheckInResults | ||||
|             .AsNoTracking() | ||||
|             .TagWith("GetEventCalendar_CheckIn") | ||||
|             .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 => | ||||
|         { | ||||
|             Date = date, | ||||
|             CheckInResult = checkInByDate.GetValueOrDefault(utcDate), | ||||
|             Statuses = statusesByDate.GetValueOrDefault(utcDate, new List<Status>()) | ||||
|         }; | ||||
|     }).ToList(); | ||||
| } | ||||
|             var utcDate = date.InUtc().Date; | ||||
|             return new DailyEventResponse | ||||
|             { | ||||
|                 Date = date, | ||||
|                 CheckInResult = checkInByDate.GetValueOrDefault(utcDate), | ||||
|                 Statuses = statusesByDate.GetValueOrDefault(utcDate, new List<Status>()) | ||||
|             }; | ||||
|         }).ToList(); | ||||
|     } | ||||
| } | ||||
| @@ -16,6 +16,7 @@ public class Status : ModelBase | ||||
|     public Guid Id { get; set; } = Guid.NewGuid(); | ||||
|     public StatusAttitude Attitude { get; set; } | ||||
|     [NotMapped] public bool IsOnline { get; set; } | ||||
|     [NotMapped] public bool IsCustomized { get; set; } = true; | ||||
|     public bool IsInvisible { get; set; } | ||||
|     public bool IsNotDisturb { get; set; } | ||||
|     [MaxLength(1024)] public string? Label { get; set; } | ||||
|   | ||||
| @@ -90,7 +90,8 @@ public class AppDatabase( | ||||
|                         PermissionService.NewPermissionNode("group:default", "global", "chat.create", true), | ||||
|                         PermissionService.NewPermissionNode("group:default", "global", "chat.messages.create", true), | ||||
|                         PermissionService.NewPermissionNode("group:default", "global", "chat.realtime.create", true), | ||||
|                         PermissionService.NewPermissionNode("group:default", "global", "accounts.statuses.create", true) | ||||
|                         PermissionService.NewPermissionNode("group:default", "global", "accounts.statuses.create", true), | ||||
|                         PermissionService.NewPermissionNode("group:default", "global", "accounts.statuses.update", true) | ||||
|                     } | ||||
|                 }); | ||||
|                 await context.SaveChangesAsync(cancellationToken); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user