2024-05-28 17:09:50 +02:00
|
|
|
using Foxnouns.Backend.Database;
|
|
|
|
using Foxnouns.Backend.Database.Models;
|
2024-07-12 17:12:24 +02:00
|
|
|
using Foxnouns.Backend.Utils;
|
2024-05-28 17:09:50 +02:00
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
using Newtonsoft.Json;
|
2024-07-13 03:09:00 +02:00
|
|
|
using NodaTime;
|
2024-05-28 17:09:50 +02:00
|
|
|
|
|
|
|
namespace Foxnouns.Backend.Services;
|
|
|
|
|
2024-10-02 00:28:07 +02:00
|
|
|
public class UserRendererService(
|
|
|
|
DatabaseContext db,
|
|
|
|
MemberRendererService memberRenderer,
|
|
|
|
Config config
|
|
|
|
)
|
2024-05-28 17:09:50 +02:00
|
|
|
{
|
2024-10-02 00:28:07 +02:00
|
|
|
public async Task<UserResponse> RenderUserAsync(
|
|
|
|
User user,
|
|
|
|
User? selfUser = null,
|
2024-07-12 17:12:24 +02:00
|
|
|
Token? token = null,
|
|
|
|
bool renderMembers = true,
|
2024-09-05 21:10:45 +02:00
|
|
|
bool renderAuthMethods = false,
|
2024-11-25 17:35:24 +01:00
|
|
|
string? overrideSid = null,
|
2024-10-02 00:28:07 +02:00
|
|
|
CancellationToken ct = default
|
2024-12-02 18:06:19 +01:00
|
|
|
) =>
|
|
|
|
await RenderUserInnerAsync(
|
|
|
|
user,
|
|
|
|
selfUser != null && selfUser.Id == user.Id,
|
|
|
|
token?.Scopes ?? [],
|
|
|
|
renderMembers,
|
|
|
|
renderAuthMethods,
|
|
|
|
overrideSid,
|
|
|
|
ct
|
|
|
|
);
|
|
|
|
|
|
|
|
public async Task<UserResponse> RenderUserInnerAsync(
|
|
|
|
User user,
|
|
|
|
bool isSelfUser,
|
|
|
|
string[] scopes,
|
|
|
|
bool renderMembers = true,
|
|
|
|
bool renderAuthMethods = false,
|
|
|
|
string? overrideSid = null,
|
|
|
|
CancellationToken ct = default
|
2024-10-02 00:28:07 +02:00
|
|
|
)
|
2024-05-28 17:09:50 +02:00
|
|
|
{
|
2024-12-02 18:06:19 +01:00
|
|
|
scopes = scopes.ExpandScopes();
|
2024-12-08 15:07:25 +01:00
|
|
|
bool tokenCanReadHiddenMembers = scopes.Contains("member.read") && isSelfUser;
|
|
|
|
bool tokenHidden = scopes.Contains("user.read_hidden") && isSelfUser;
|
|
|
|
bool tokenPrivileged = scopes.Contains("user.read_privileged") && isSelfUser;
|
2024-05-28 17:09:50 +02:00
|
|
|
|
2024-10-02 00:28:07 +02:00
|
|
|
renderMembers = renderMembers && (!user.ListHidden || tokenCanReadHiddenMembers);
|
2024-07-13 03:09:00 +02:00
|
|
|
renderAuthMethods = renderAuthMethods && tokenPrivileged;
|
2024-07-12 17:12:24 +02:00
|
|
|
|
2024-10-02 00:28:07 +02:00
|
|
|
IEnumerable<Member> members = renderMembers
|
|
|
|
? await db.Members.Where(m => m.UserId == user.Id).OrderBy(m => m.Name).ToListAsync(ct)
|
|
|
|
: [];
|
2024-07-12 17:12:24 +02:00
|
|
|
// Unless the user is requesting their own members AND the token can read hidden members, we filter out unlisted members.
|
2024-10-02 00:28:07 +02:00
|
|
|
if (!(isSelfUser && tokenCanReadHiddenMembers))
|
|
|
|
members = members.Where(m => !m.Unlisted);
|
2024-07-12 17:12:24 +02:00
|
|
|
|
2024-12-08 15:07:25 +01:00
|
|
|
List<UserFlag> flags = await db
|
2024-10-02 00:28:07 +02:00
|
|
|
.UserFlags.Where(f => f.UserId == user.Id)
|
|
|
|
.OrderBy(f => f.Id)
|
|
|
|
.ToListAsync(ct);
|
2024-09-27 14:48:09 +02:00
|
|
|
|
2024-12-08 15:07:25 +01:00
|
|
|
List<AuthMethod> authMethods = renderAuthMethods
|
2024-10-02 00:28:07 +02:00
|
|
|
? await db
|
|
|
|
.AuthMethods.Where(a => a.UserId == user.Id)
|
2024-07-12 17:12:24 +02:00
|
|
|
.Include(a => a.FediverseApplication)
|
2024-09-05 21:10:45 +02:00
|
|
|
.ToListAsync(ct)
|
2024-07-12 17:12:24 +02:00
|
|
|
: [];
|
2024-05-28 17:09:50 +02:00
|
|
|
|
2024-11-24 22:19:53 +01:00
|
|
|
int? utcOffset = null;
|
|
|
|
if (
|
|
|
|
user.Timezone != null
|
2024-12-08 15:07:25 +01:00
|
|
|
&& TimeZoneInfo.TryFindSystemTimeZoneById(user.Timezone, out TimeZoneInfo? tz)
|
2024-11-24 22:19:53 +01:00
|
|
|
)
|
2024-12-08 15:17:18 +01:00
|
|
|
{
|
2024-11-24 22:19:53 +01:00
|
|
|
utcOffset = (int)tz.GetUtcOffset(DateTimeOffset.UtcNow).TotalSeconds;
|
2024-12-08 15:17:18 +01:00
|
|
|
}
|
2024-11-24 22:19:53 +01:00
|
|
|
|
2024-05-28 17:09:50 +02:00
|
|
|
return new UserResponse(
|
2024-10-02 00:28:07 +02:00
|
|
|
user.Id,
|
2024-11-25 17:35:24 +01:00
|
|
|
overrideSid ?? user.Sid,
|
2024-10-02 00:28:07 +02:00
|
|
|
user.Username,
|
|
|
|
user.DisplayName,
|
|
|
|
user.Bio,
|
|
|
|
user.MemberTitle,
|
|
|
|
AvatarUrlFor(user),
|
2024-09-26 17:09:27 +02:00
|
|
|
user.Links,
|
2024-10-02 00:28:07 +02:00
|
|
|
user.Names,
|
|
|
|
user.Pronouns,
|
|
|
|
user.Fields,
|
|
|
|
user.CustomPreferences,
|
2024-09-27 14:48:09 +02:00
|
|
|
flags.Select(f => RenderPrideFlag(f.PrideFlag)),
|
2024-11-24 22:19:53 +01:00
|
|
|
utcOffset,
|
2024-10-01 16:06:02 +02:00
|
|
|
user.Role,
|
2024-10-02 00:28:07 +02:00
|
|
|
renderMembers
|
|
|
|
? members.Select(m => memberRenderer.RenderPartialMember(m, tokenHidden))
|
|
|
|
: null,
|
2024-11-04 22:04:04 +01:00
|
|
|
renderAuthMethods ? authMethods.Select(RenderAuthMethod) : null,
|
2024-07-13 19:38:40 +02:00
|
|
|
tokenHidden ? user.ListHidden : null,
|
2024-09-26 17:09:27 +02:00
|
|
|
tokenHidden ? user.LastActive : null,
|
2024-11-24 22:19:53 +01:00
|
|
|
tokenHidden ? user.LastSidReroll : null,
|
|
|
|
tokenHidden ? user.Timezone ?? "<none>" : null
|
2024-07-12 17:12:24 +02:00
|
|
|
);
|
2024-05-28 17:09:50 +02:00
|
|
|
}
|
|
|
|
|
2024-11-04 22:04:04 +01:00
|
|
|
public static AuthMethodResponse RenderAuthMethod(AuthMethod a) =>
|
|
|
|
new(
|
|
|
|
a.Id,
|
|
|
|
a.AuthType,
|
|
|
|
a.RemoteId,
|
|
|
|
a.FediverseApplication != null
|
|
|
|
? $"@{a.RemoteUsername}@{a.FediverseApplication.Domain}"
|
|
|
|
: a.RemoteUsername
|
|
|
|
);
|
|
|
|
|
2024-07-13 19:38:40 +02:00
|
|
|
public PartialUser RenderPartialUser(User user) =>
|
2024-10-02 00:28:07 +02:00
|
|
|
new(
|
|
|
|
user.Id,
|
|
|
|
user.Sid,
|
|
|
|
user.Username,
|
|
|
|
user.DisplayName,
|
|
|
|
AvatarUrlFor(user),
|
|
|
|
user.CustomPreferences
|
|
|
|
);
|
2024-07-13 19:38:40 +02:00
|
|
|
|
2024-12-02 18:06:19 +01:00
|
|
|
public string? AvatarUrlFor(User user) =>
|
2024-10-02 00:28:07 +02:00
|
|
|
user.Avatar != null
|
|
|
|
? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp"
|
|
|
|
: null;
|
2024-09-27 14:48:09 +02:00
|
|
|
|
2024-12-02 18:06:19 +01:00
|
|
|
public string ImageUrlFor(PrideFlag flag) => $"{config.MediaBaseUrl}/flags/{flag.Hash}.webp";
|
2024-07-12 17:12:24 +02:00
|
|
|
|
2024-05-28 17:09:50 +02:00
|
|
|
public record UserResponse(
|
|
|
|
Snowflake Id,
|
2024-09-26 15:26:52 +02:00
|
|
|
string Sid,
|
2024-05-28 17:09:50 +02:00
|
|
|
string Username,
|
|
|
|
string? DisplayName,
|
|
|
|
string? Bio,
|
|
|
|
string? MemberTitle,
|
|
|
|
string? AvatarUrl,
|
|
|
|
string[] Links,
|
|
|
|
IEnumerable<FieldEntry> Names,
|
|
|
|
IEnumerable<Pronoun> Pronouns,
|
|
|
|
IEnumerable<Field> Fields,
|
2024-08-22 15:13:46 +02:00
|
|
|
Dictionary<Snowflake, User.CustomPreference> CustomPreferences,
|
2024-09-27 14:48:09 +02:00
|
|
|
IEnumerable<PrideFlagResponse> Flags,
|
2024-11-24 22:19:53 +01:00
|
|
|
int? UtcOffset,
|
2024-10-02 00:28:07 +02:00
|
|
|
[property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] UserRole Role,
|
2024-07-12 17:12:24 +02:00
|
|
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
2024-10-02 00:28:07 +02:00
|
|
|
IEnumerable<MemberRendererService.PartialMember>? Members,
|
2024-07-12 17:12:24 +02:00
|
|
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
2024-11-04 22:04:04 +01:00
|
|
|
IEnumerable<AuthMethodResponse>? AuthMethods,
|
2024-07-13 03:09:00 +02:00
|
|
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
2024-10-02 00:28:07 +02:00
|
|
|
bool? MemberListHidden,
|
|
|
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Instant? LastActive,
|
2024-07-13 19:38:40 +02:00
|
|
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
2024-11-24 22:19:53 +01:00
|
|
|
Instant? LastSidReroll,
|
|
|
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Timezone
|
2024-07-12 17:12:24 +02:00
|
|
|
);
|
|
|
|
|
2024-11-04 22:04:04 +01:00
|
|
|
public record AuthMethodResponse(
|
2024-07-12 17:12:24 +02:00
|
|
|
Snowflake Id,
|
2024-10-02 00:28:07 +02:00
|
|
|
[property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] AuthType Type,
|
2024-07-12 17:12:24 +02:00
|
|
|
string RemoteId,
|
|
|
|
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
2024-11-03 13:53:16 +01:00
|
|
|
string? RemoteUsername
|
2024-05-28 17:09:50 +02:00
|
|
|
);
|
2024-07-13 19:38:40 +02:00
|
|
|
|
|
|
|
public record PartialUser(
|
|
|
|
Snowflake Id,
|
2024-09-26 15:26:52 +02:00
|
|
|
string Sid,
|
2024-07-13 19:38:40 +02:00
|
|
|
string Username,
|
|
|
|
string? DisplayName,
|
2024-09-25 19:48:05 +02:00
|
|
|
string? AvatarUrl,
|
|
|
|
Dictionary<Snowflake, User.CustomPreference> CustomPreferences
|
2024-07-13 19:38:40 +02:00
|
|
|
);
|
2024-09-27 14:48:09 +02:00
|
|
|
|
|
|
|
public PrideFlagResponse RenderPrideFlag(PrideFlag flag) =>
|
|
|
|
new(flag.Id, ImageUrlFor(flag), flag.Name, flag.Description);
|
|
|
|
|
|
|
|
public record PrideFlagResponse(
|
|
|
|
Snowflake Id,
|
|
|
|
string ImageUrl,
|
|
|
|
string Name,
|
2024-10-02 00:28:07 +02:00
|
|
|
string? Description
|
|
|
|
);
|
|
|
|
}
|