162 lines
5.8 KiB
C#
162 lines
5.8 KiB
C#
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
using Foxnouns.Backend.Database;
|
|
using Foxnouns.Backend.Database.Models;
|
|
using Foxnouns.Backend.Dto;
|
|
using Foxnouns.Backend.Utils;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Foxnouns.Backend.Services;
|
|
|
|
public class UserRendererService(
|
|
DatabaseContext db,
|
|
MemberRendererService memberRenderer,
|
|
Config config
|
|
)
|
|
{
|
|
public async Task<UserResponse> RenderUserAsync(
|
|
User user,
|
|
User? selfUser = null,
|
|
Token? token = null,
|
|
bool renderMembers = true,
|
|
bool renderAuthMethods = false,
|
|
string? overrideSid = null,
|
|
CancellationToken ct = default
|
|
) =>
|
|
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
|
|
)
|
|
{
|
|
scopes = scopes.ExpandScopes();
|
|
bool tokenCanReadHiddenMembers = scopes.Contains("member.read") && isSelfUser;
|
|
bool tokenHidden = scopes.Contains("user.read_hidden") && isSelfUser;
|
|
bool tokenPrivileged = scopes.Contains("user.read_privileged") && isSelfUser;
|
|
|
|
renderMembers = renderMembers && (!user.ListHidden || tokenCanReadHiddenMembers);
|
|
renderAuthMethods = renderAuthMethods && tokenPrivileged;
|
|
|
|
IEnumerable<Member> members = renderMembers
|
|
? await db.Members.Where(m => m.UserId == user.Id).OrderBy(m => m.Name).ToListAsync(ct)
|
|
: [];
|
|
// Unless the user is requesting their own members AND the token can read hidden members, we filter out unlisted members.
|
|
if (!(isSelfUser && tokenCanReadHiddenMembers))
|
|
members = members.Where(m => !m.Unlisted);
|
|
|
|
List<UserFlag> flags = await db
|
|
.UserFlags.Where(f => f.UserId == user.Id)
|
|
.OrderBy(f => f.Id)
|
|
.ToListAsync(ct);
|
|
|
|
List<AuthMethod> authMethods = renderAuthMethods
|
|
? await db
|
|
.AuthMethods.Where(a => a.UserId == user.Id)
|
|
.Include(a => a.FediverseApplication)
|
|
.ToListAsync(ct)
|
|
: [];
|
|
|
|
int? utcOffset = null;
|
|
if (
|
|
user.Timezone != null
|
|
&& TimeZoneInfo.TryFindSystemTimeZoneById(user.Timezone, out TimeZoneInfo? tz)
|
|
)
|
|
{
|
|
utcOffset = (int)tz.GetUtcOffset(DateTimeOffset.UtcNow).TotalSeconds;
|
|
}
|
|
|
|
return new UserResponse(
|
|
user.Id,
|
|
overrideSid ?? user.Sid,
|
|
user.Username,
|
|
user.DisplayName,
|
|
user.Bio,
|
|
user.MemberTitle,
|
|
AvatarUrlFor(user),
|
|
user.Links,
|
|
user.Names,
|
|
user.Pronouns,
|
|
user.Fields,
|
|
user.CustomPreferences.Select(x => (x.Key, RenderCustomPreference(x.Value)))
|
|
.ToDictionary(),
|
|
flags.Select(f => RenderPrideFlag(f.PrideFlag)),
|
|
utcOffset,
|
|
user.Role,
|
|
renderMembers
|
|
? members.Select(m => memberRenderer.RenderPartialMember(m, tokenHidden))
|
|
: null,
|
|
renderAuthMethods ? authMethods.Select(RenderAuthMethod) : null,
|
|
tokenHidden ? user.ListHidden : null,
|
|
tokenHidden ? user.LastActive : null,
|
|
tokenHidden ? user.LastSidReroll : null,
|
|
tokenHidden ? user.Timezone ?? "<none>" : null,
|
|
tokenHidden ? user is { Deleted: true, DeletedBy: not null } : null,
|
|
tokenHidden ? user.Deleted : null
|
|
);
|
|
}
|
|
|
|
public static AuthMethodResponse RenderAuthMethod(AuthMethod a) =>
|
|
new(
|
|
a.Id,
|
|
a.AuthType,
|
|
a.RemoteId,
|
|
a.FediverseApplication != null
|
|
? $"@{a.RemoteUsername}@{a.FediverseApplication.Domain}"
|
|
: a.RemoteUsername
|
|
);
|
|
|
|
public static CustomPreferenceResponse RenderCustomPreference(User.CustomPreference pref) =>
|
|
new(pref.Icon, pref.Tooltip, pref.Muted, pref.Favourite, pref.Size);
|
|
|
|
public static Dictionary<Snowflake, CustomPreferenceResponse> RenderCustomPreferences(
|
|
User user
|
|
) =>
|
|
user.CustomPreferences.Select(x => (x.Key, RenderCustomPreference(x.Value))).ToDictionary();
|
|
|
|
public PartialUser RenderPartialUser(User user) =>
|
|
new(
|
|
user.Id,
|
|
user.Sid,
|
|
user.Username,
|
|
user.DisplayName,
|
|
AvatarUrlFor(user),
|
|
user.CustomPreferences
|
|
);
|
|
|
|
public string? AvatarUrlFor(User user) =>
|
|
user.Avatar != null
|
|
? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp"
|
|
: null;
|
|
|
|
public string? ImageUrlFor(PrideFlag flag) =>
|
|
flag.Hash != null ? $"{config.MediaBaseUrl}/flags/{flag.Hash}.webp" : null;
|
|
|
|
public PrideFlagResponse RenderPrideFlag(PrideFlag flag) =>
|
|
new(flag.Id, ImageUrlFor(flag), flag.Name, flag.Description);
|
|
}
|