diff --git a/Foxnouns.Backend/Controllers/V1/V1ReadController.cs b/Foxnouns.Backend/Controllers/V1/V1ReadController.cs index 5f69c20..327f03e 100644 --- a/Foxnouns.Backend/Controllers/V1/V1ReadController.cs +++ b/Foxnouns.Backend/Controllers/V1/V1ReadController.cs @@ -15,6 +15,7 @@ using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Dto.V1; +using Foxnouns.Backend.Middleware; using Foxnouns.Backend.Services.V1; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -28,6 +29,14 @@ public class V1ReadController( DatabaseContext db ) : ApiControllerBase { + [HttpGet("users/@me")] + [Authorize("identify")] + public async Task GetMeAsync(CancellationToken ct = default) + { + User user = await usersV1Service.ResolveUserAsync("@me", CurrentToken, ct); + return Ok(await usersV1Service.RenderCurrentUserAsync(user, ct)); + } + [HttpGet("users/{userRef}")] public async Task GetUserAsync(string userRef, CancellationToken ct = default) { diff --git a/Foxnouns.Backend/Dto/V1/User.cs b/Foxnouns.Backend/Dto/V1/User.cs index c212d97..e80a355 100644 --- a/Foxnouns.Backend/Dto/V1/User.cs +++ b/Foxnouns.Backend/Dto/V1/User.cs @@ -20,6 +20,7 @@ using Foxnouns.Backend.Services.V1; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; +using NodaTime; namespace Foxnouns.Backend.Dto.V1; @@ -42,6 +43,39 @@ public record UserResponse( Dictionary CustomPreferences ); +public record CurrentUserResponse( + string Id, + Snowflake IdNew, + string Sid, + string Name, + string? DisplayName, + string? Bio, + string? MemberTitle, + string? Avatar, + string[] Links, + FieldEntry[] Names, + PronounEntry[] Pronouns, + ProfileField[] Fields, + PrideFlag[] Flags, + PartialMember[] Members, + int? UtcOffset, + Dictionary CustomPreferences, + Instant CreatedAt, + string? Timezone, + bool IsAdmin, + bool ListPrivate, + Instant LastSidReroll, + string? Discord, + string? DiscordUsername, + string? Google, + string? GoogleUsername, + string? Tumblr, + string? TumblrUsername, + string? Fediverse, + string? FediverseUsername, + string? FediverseInstance +); + public record CustomPreference( string Icon, string Tooltip, diff --git a/Foxnouns.Backend/Services/V1/UsersV1Service.cs b/Foxnouns.Backend/Services/V1/UsersV1Service.cs index 34163a6..1f2ad79 100644 --- a/Foxnouns.Backend/Services/V1/UsersV1Service.cs +++ b/Foxnouns.Backend/Services/V1/UsersV1Service.cs @@ -122,6 +122,84 @@ public class UsersV1Service(DatabaseContext db) ); } + public async Task RenderCurrentUserAsync( + User user, + CancellationToken ct = default + ) + { + List members = await db + .Members.Where(m => m.UserId == user.Id) + .OrderBy(m => m.Name) + .ToListAsync(ct); + + List flags = await db + .UserFlags.Where(f => f.UserId == user.Id) + .OrderBy(f => f.Id) + .ToListAsync(ct); + + int? utcOffset = null; + if ( + user.Timezone != null + && TimeZoneInfo.TryFindSystemTimeZoneById(user.Timezone, out TimeZoneInfo? tz) + ) + { + utcOffset = (int)tz.GetUtcOffset(DateTimeOffset.UtcNow).TotalSeconds; + } + + List authMethods = await db + .AuthMethods.Include(a => a.FediverseApplication) + .Where(a => a.UserId == user.Id) + .OrderBy(a => a.Id) + .ToListAsync(ct); + + AuthMethod? discord = authMethods.FirstOrDefault(a => a.AuthType is AuthType.Discord); + AuthMethod? google = authMethods.FirstOrDefault(a => a.AuthType is AuthType.Google); + AuthMethod? tumblr = authMethods.FirstOrDefault(a => a.AuthType is AuthType.Tumblr); + AuthMethod? fediverse = authMethods.FirstOrDefault(a => a.AuthType is AuthType.Fediverse); + + return new CurrentUserResponse( + user.LegacyId, + user.Id, + user.Sid, + user.Username, + user.DisplayName, + user.Bio, + user.MemberTitle, + user.Avatar, + user.Links, + Names: FieldEntry.FromEntries(user.Names, user.CustomPreferences), + Pronouns: PronounEntry.FromPronouns(user.Pronouns, user.CustomPreferences), + Fields: ProfileField.FromFields(user.Fields, user.CustomPreferences), + Flags: flags + .Where(f => f.PrideFlag.Hash != null) + .Select(f => new PrideFlag( + f.PrideFlag.LegacyId, + f.PrideFlag.Id, + f.PrideFlag.Hash!, + f.PrideFlag.Name, + f.PrideFlag.Description + )) + .ToArray(), + Members: members.Select(m => RenderPartialMember(m, user.CustomPreferences)).ToArray(), + utcOffset, + CustomPreferences: RenderCustomPreferences(user.CustomPreferences), + user.Id.Time, + user.Timezone, + user.Role is UserRole.Admin, + user.ListHidden, + user.LastSidReroll, + discord?.RemoteId, + discord?.RemoteUsername, + google?.RemoteId, + google?.RemoteUsername, + tumblr?.RemoteId, + tumblr?.RemoteUsername, + fediverse?.RemoteId, + fediverse?.RemoteUsername, + fediverse?.FediverseApplication?.Domain + ); + } + private static Dictionary RenderCustomPreferences( Dictionary customPreferences ) =>