Compare commits

..

35 Commits

Author SHA1 Message Date
CakeAndBanana
884ad25c33 update submodule 2025-10-19 21:21:19 +02:00
CakeAndBanana
3926f3be89 remove blankspace 2025-10-19 21:04:22 +02:00
CakeAndBanana
d28198a9c8 fix migrations 2025-10-19 21:04:12 +02:00
CakeAndBanana
7cc6918b12 updated submodule 2025-10-19 20:58:40 +02:00
CakeAndBanana
dba7536a7f Added tags on call for user profile calls, added disabled on syncshell profiles. reworked the calls 2025-10-19 20:58:07 +02:00
CakeAndBanana
f35c0c4c2a updated submodule 2025-10-19 18:40:49 +02:00
CakeAndBanana
ad00f7b078 Changes in database for tags to be array integers instead of strings 2025-10-19 18:36:08 +02:00
CakeAndBanana
c30190704f Changed get/set profile with more safe handling 2025-10-19 17:53:20 +02:00
CakeAndBanana
bab81aaf51 Added null checks 2025-10-19 17:39:36 +02:00
CakeAndBanana
4fdc2a5c29 FIx to attempt to get group 2025-10-19 17:30:16 +02:00
CakeAndBanana
bbcf98576e Fixed so it can search on alias better 2025-10-19 17:22:25 +02:00
583f1a8957 Merge pull request 'Fixed some issues with profiles on groups' (#15) from fix-profiles-syncshell into master
Reviewed-on: #15
Reviewed-by: defnotken <defnotken@noreply.git.lightless-sync.org>
2025-10-17 00:09:46 +02:00
defnotken
2ebdd6e0c7 Merge branch 'master' into fix-profiles-syncshell 2025-10-16 16:32:31 -05:00
CakeAndBanana
2407259769 update submodule 2025-10-16 23:15:51 +02:00
03af0b853c Lightfinder-profiles
Reviewed-on: #18
2025-10-16 22:25:51 +02:00
azyges
53f663fcbf zzz 2025-10-17 00:32:43 +09:00
azyges
47a94cb79f shivering my timbers 2025-10-17 00:20:26 +09:00
f933b40368 LightfinderMetrics
Reviewed-on: #14
Reviewed-by: defnotken <defnotken@noreply.git.lightless-sync.org>
2025-10-16 00:12:56 +02:00
CakeAndBanana
b670cb69dd Updated submodule for dto 2025-10-15 23:25:54 +02:00
CakeAndBanana
50f3b0d644 Added default values for nsfw and disabled on set profile for groups 2025-10-15 23:24:43 +02:00
CakeAndBanana
3a6203844e Added support for nsfw and disabled for groups 2025-10-15 23:22:07 +02:00
CakeAndBanana
80086f6817 Merge branch 'fix-profiles-syncshell' of https://git.lightless-sync.org/Lightless-Sync/LightlessServer into fix-profiles-syncshell 2025-10-15 23:09:12 +02:00
7e565ff85e CDN on shards
Reviewed-on: #17
2025-10-15 21:32:58 +02:00
defnotken
b36b1fb8f9 Add nsfw and disabled in db 2025-10-15 12:39:46 -05:00
CakeAndBanana
707c565ea9 Inverted boolean for syncshells 2025-10-15 04:02:27 +02:00
CakeAndBanana
6beda853f7 Added metric of broadcasted syncshells 2025-10-15 03:58:00 +02:00
CakeAndBanana
23dc6d7ef4 Merge branch 'metrics-lightfinder' of https://git.lightless-sync.org/Lightless-Sync/LightlessServer into metrics-lightfinder 2025-10-15 02:47:27 +02:00
CakeAndBanana
f686f7a6da Added lightfinder metric on startup 2025-10-15 02:47:18 +02:00
CakeAndBanana
280cc2ebbb Fix submodule 2025-10-14 20:21:25 +02:00
CakeAndBanana
7909850ad5 Changed cancellation token. 2025-10-14 19:01:58 +02:00
CakeAndBanana
f60994fa58 update 2025-10-14 19:00:56 +02:00
CakeAndBanana
96f230cd21 merge 2025-10-14 19:00:45 +02:00
0fe1a43fb2 Merge branch 'master' into metrics-lightfinder 2025-10-14 18:56:37 +02:00
CakeAndBanana
43b9c6f90e Added lightfinder users in metrics 2025-10-14 18:45:57 +02:00
CakeAndBanana
59f3739b9c Fixed some issues with profiles on groups 2025-10-14 18:19:46 +02:00
16 changed files with 2833 additions and 86 deletions

2
.gitmodules vendored
View File

@@ -1,4 +1,4 @@
[submodule "LightlessAPI"]
path = LightlessAPI
url = https://git.lightless-sync.org/Lightless-Sync/LightlessAPI
branch = main
branch = main

View File

@@ -209,7 +209,7 @@ public partial class LightlessHub
if (isOwnerResult.ReferredGroup == null) return (false, null);
var groupPairSelf = await DbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == UserUID).ConfigureAwait(false);
var groupPairSelf = await DbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid || g.Group.Alias == gid && g.GroupUserUID == UserUID).ConfigureAwait(false);
if (groupPairSelf == null || !groupPairSelf.IsModerator) return (false, null);
return (true, isOwnerResult.ReferredGroup);
@@ -217,7 +217,7 @@ public partial class LightlessHub
private async Task<(bool isValid, Group ReferredGroup)> TryValidateOwner(string gid)
{
var group = await DbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
var group = await DbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid || g.Alias == gid).ConfigureAwait(false);
if (group == null) return (false, null);
return (string.Equals(group.OwnerUID, UserUID, StringComparison.Ordinal), group);

View File

@@ -9,6 +9,8 @@ using LightlessSyncShared.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System.Security.Cryptography;
namespace LightlessSyncServer.Hubs;
@@ -745,27 +747,39 @@ public partial class LightlessHub
var cancellationToken = RequestAbortedToken;
var data = await DbContext.GroupProfiles
.FirstOrDefaultAsync(g => g.GroupGID == dto.Group.GID)
.ConfigureAwait(false);
var profileDto = new GroupProfileDto(dto.Group, Description: null, Tags: null, PictureBase64: null);
if (data is not null)
if (dto?.Group == null)
{
profileDto = profileDto with
{
Description = data.Description,
Tags = data.Tags,
PictureBase64 = data.Base64GroupProfileImage,
};
await Clients.User(UserUID)
.Client_GroupSendProfile(profileDto)
.ConfigureAwait(false);
_logger.LogCallWarning(LightlessHubLogger.Args("GroupGetProfile: dto.Group is null"));
return new GroupProfileDto(Group: null, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false);
}
return profileDto;
var data = await DbContext.GroupProfiles
.Include(gp => gp.Group)
.FirstOrDefaultAsync(
g => g.Group.GID == dto.Group.GID || g.Group.Alias == dto.Group.AliasOrGID,
cancellationToken
)
.ConfigureAwait(false);
if (data == null)
{
return new GroupProfileDto(dto.Group, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false);
}
if (data.ProfileDisabled)
{
return new GroupProfileDto(Group: dto.Group, Description: "This profile was permanently disabled", Tags: [], PictureBase64: null, IsNsfw: false, IsDisabled: true);
}
try
{
return data.ToDTO();
}
catch (Exception ex)
{
_logger.LogCallWarning(LightlessHubLogger.Args(ex, "GroupGetProfile: failed to map GroupProfileDto for {Group}", dto.Group.GID ?? dto.Group.AliasOrGID));
return new GroupProfileDto(dto.Group, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false);
}
}
[Authorize(Policy = "Identified")]
@@ -773,38 +787,88 @@ public partial class LightlessHub
{
_logger.LogCallInfo(LightlessHubLogger.Args(dto));
var cancellationToken = RequestAbortedToken;
if (dto.Group == null) return;
var (hasRights, group) = await TryValidateGroupModeratorOrOwner(dto.Group.GID).ConfigureAwait(false);
if (!hasRights) return;
var groupProfileDb = await DbContext.GroupProfiles
.FirstOrDefaultAsync(g => g.GroupGID == dto.Group.GID,
RequestAbortedToken)
.FirstOrDefaultAsync(g => g.Group.GID == dto.Group.GID || g.Group.Alias == dto.Group.GID,
cancellationToken)
.ConfigureAwait(false);
if (groupProfileDb != null)
if (!string.IsNullOrEmpty(dto.PictureBase64))
{
groupProfileDb.Description = dto.Description;
groupProfileDb.Tags = dto.Tags;
groupProfileDb.Base64GroupProfileImage = dto.PictureBase64;
byte[] imageData;
try
{
imageData = Convert.FromBase64String(dto.PictureBase64);
}
catch (FormatException)
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "The provided image is not a valid Base64 string.").ConfigureAwait(false);
return;
}
MemoryStream ms = new(imageData);
await using (ms.ConfigureAwait(false))
{
var format = await Image.DetectFormatAsync(ms, RequestAbortedToken).ConfigureAwait(false);
if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is not in PNG format").ConfigureAwait(false);
return;
}
using var image = Image.Load<Rgba32>(imageData);
if (image.Width > 512 || image.Height > 512 || (imageData.Length > 2000 * 1024))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is larger than 512x512 or more than 2MiB").ConfigureAwait(false);
return;
}
}
}
if (groupProfileDb == null)
{
groupProfileDb = new GroupProfile
{
GroupGID = dto.Group.GID,
ProfileDisabled = false,
IsNSFW = dto.IsNsfw ?? false,
};
groupProfileDb.UpdateProfileFromDto(dto);
await DbContext.GroupProfiles.AddAsync(groupProfileDb, cancellationToken).ConfigureAwait(false);
}
else
{
var groupProfile = new GroupProfile
if (groupProfileDb?.ProfileDisabled ?? false)
{
GroupGID = dto.Group.GID,
Description = dto.Description,
Tags = dto.Tags,
Base64GroupProfileImage = dto.PictureBase64,
};
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile was permanently disabled and cannot be edited").ConfigureAwait(false);
return;
}
await DbContext.GroupProfiles.AddAsync(groupProfile,
RequestAbortedToken)
groupProfileDb.UpdateProfileFromDto(dto);
var userIds = await DbContext.GroupPairs
.Where(p => p.GroupGID == groupProfileDb.GroupGID)
.Select(p => p.GroupUserUID)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
if (userIds.Count > 0)
{
var profileDto = groupProfileDb.ToDTO();
await Clients.Users(userIds).Client_GroupSendProfile(profileDto)
.ConfigureAwait(false);
}
}
await DbContext.SaveChangesAsync(RequestAbortedToken).ConfigureAwait(false);
await DbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
[Authorize(Policy = "Identified")]
@@ -990,6 +1054,4 @@ public partial class LightlessHub
return results;
}
}

View File

@@ -72,7 +72,7 @@ public partial class LightlessHub
var existingData = await GetPairInfo(UserUID, otherUser.UID).ConfigureAwait(false);
var permissions = existingData?.OwnPermissions;
if (permissions == null || !permissions.Sticky)
if (permissions == null || !permissions.Sticky)
{
var ownDefaultPermissions = await DbContext.UserDefaultPreferredPermissions.AsNoTracking().SingleOrDefaultAsync(f => f.UserUID == UserUID, cancellationToken: RequestAbortedToken).ConfigureAwait(false);
@@ -523,6 +523,52 @@ public partial class LightlessHub
}
}
private async Task<(BroadcastRedisEntry? Entry, TimeSpan? Expiry)> TryGetBroadcastEntryAsync(string hashedCid)
{
var key = _broadcastConfiguration.BuildRedisKey(hashedCid);
RedisValueWithExpiry value;
try
{
value = await _redis.Database.StringGetWithExpiryAsync(key).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogCallWarning(LightlessHubLogger.Args("LightfinderProfileLookupFailed", "CID", hashedCid, "Error", ex));
return (null, null);
}
if (value.Value.IsNullOrEmpty || value.Expiry is null || value.Expiry <= TimeSpan.Zero)
{
return (null, value.Expiry);
}
BroadcastRedisEntry? entry;
try
{
entry = JsonSerializer.Deserialize<BroadcastRedisEntry>(value.Value!);
}
catch (Exception ex)
{
_logger.LogCallWarning(LightlessHubLogger.Args("LightfinderProfileDeserializeFailed", "CID", hashedCid, "Raw", value.Value.ToString(), "Error", ex));
return (null, value.Expiry);
}
if (entry is null || !string.Equals(entry.HashedCID, hashedCid, StringComparison.Ordinal))
{
_logger.LogCallWarning(LightlessHubLogger.Args("LightfinderProfileEntryMismatch", "CID", hashedCid, "EntryCID", entry?.HashedCID ?? "null"));
return (null, value.Expiry);
}
return (entry, value.Expiry);
}
private static bool HasActiveBroadcast(BroadcastRedisEntry? entry, TimeSpan? expiry) =>
entry?.HasOwner() == true && expiry.HasValue && expiry.Value > TimeSpan.Zero;
private static bool IsActiveBroadcastForUser(BroadcastRedisEntry? entry, TimeSpan? expiry, string userUid) =>
HasActiveBroadcast(entry, expiry) && entry!.OwnedBy(userUid);
private static bool IsValidHashedCid(string? cid)
{
if (string.IsNullOrWhiteSpace(cid))
@@ -780,16 +826,107 @@ public partial class LightlessHub
if (!allUserPairs.Contains(user.User.UID, StringComparer.Ordinal) && !string.Equals(user.User.UID, UserUID, StringComparison.Ordinal))
{
return new UserProfileDto(user.User, false, null, null, "Due to the pause status you cannot access this users profile.");
return new UserProfileDto(user.User, false, null, null, "Due to the pause status you cannot access this users profile.", []);
}
var data = await DbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == user.User.UID, cancellationToken: RequestAbortedToken).ConfigureAwait(false);
if (data == null) return new UserProfileDto(user.User, false, null, null, null);
if (data == null) return new UserProfileDto(user.User, false, null, null, null, []);
if (data.FlaggedForReport) return new UserProfileDto(user.User, true, null, null, "This profile is flagged for report and pending evaluation");
if (data.ProfileDisabled) return new UserProfileDto(user.User, true, null, null, "This profile was permanently disabled");
if (data.FlaggedForReport) return new UserProfileDto(user.User, true, null, null, "This profile is flagged for report and pending evaluation", []);
if (data.ProfileDisabled) return new UserProfileDto(user.User, true, null, null, "This profile was permanently disabled", []);
return new UserProfileDto(user.User, false, data.IsNSFW, data.Base64ProfileImage, data.UserDescription);
return new UserProfileDto(user.User, false, data.IsNSFW, data.Base64ProfileImage, data.UserDescription, data.Tags);
}
[Authorize(Policy = "Identified")]
public async Task<UserProfileDto?> UserGetLightfinderProfile(string hashedCid)
{
_logger.LogCallInfo(LightlessHubLogger.Args("LightfinderProfile", hashedCid));
if (!_broadcastConfiguration.EnableBroadcasting)
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Lightfinder is currently disabled.").ConfigureAwait(false);
return null;
}
if (!IsValidHashedCid(hashedCid))
{
_logger.LogCallWarning(LightlessHubLogger.Args("LightfinderProfileInvalidCid", hashedCid));
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "Invalid Lightfinder target.").ConfigureAwait(false);
return null;
}
var viewerCid = UserCharaIdent;
if (!IsValidHashedCid(viewerCid))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "You must be using Lightfinder to open player profiles.").ConfigureAwait(false);
return null;
}
var (viewerEntry, viewerExpiry) = await TryGetBroadcastEntryAsync(viewerCid).ConfigureAwait(false);
if (!IsActiveBroadcastForUser(viewerEntry, viewerExpiry, UserUID))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "You must be using Lightfinder to open player profiles.").ConfigureAwait(false);
return null;
}
var (targetEntry, targetExpiry) = await TryGetBroadcastEntryAsync(hashedCid).ConfigureAwait(false);
if (!HasActiveBroadcast(targetEntry, targetExpiry))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "That player is not currently using Lightfinder.").ConfigureAwait(false);
return null;
}
if (string.IsNullOrEmpty(targetEntry!.OwnerUID))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "That player is not currently using Lightfinder.").ConfigureAwait(false);
return null;
}
var targetUser = await DbContext.Users.AsNoTracking()
.SingleOrDefaultAsync(u => u.UID == targetEntry.OwnerUID, cancellationToken: RequestAbortedToken)
.ConfigureAwait(false);
if (targetUser == null)
{
_logger.LogCallWarning(LightlessHubLogger.Args("LightfinderProfileMissingUser", hashedCid, "OwnerUID", targetEntry.OwnerUID));
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Warning, "Unable to load the players profile at this time.").ConfigureAwait(false);
return null;
}
var displayAlias = string.IsNullOrWhiteSpace(targetUser.Alias)
? "LightfinderUser"
: targetUser.Alias;
var userData = new UserData(
UID: hashedCid,
Alias: displayAlias,
IsAdmin: false,
IsModerator: false,
HasVanity: false,
TextColorHex: targetUser.TextColorHex,
TextGlowColorHex: targetUser.TextGlowColorHex);
var profile = await DbContext.UserProfileData.AsNoTracking()
.SingleOrDefaultAsync(u => u.UserUID == targetEntry.OwnerUID, cancellationToken: RequestAbortedToken)
.ConfigureAwait(false);
if (profile == null)
{
return new UserProfileDto(userData, false, null, null, null, []);
}
if (profile.FlaggedForReport)
{
return new UserProfileDto(userData, true, null, null, "This profile is flagged for report and pending evaluation", []);
}
if (profile.ProfileDisabled)
{
return new UserProfileDto(userData, true, null, null, "This profile was permanently disabled", []);
}
return new UserProfileDto(userData, false, profile.IsNSFW, profile.Base64ProfileImage, profile.UserDescription, profile.Tags);
}
[Authorize(Policy = "Identified")]
@@ -989,76 +1126,78 @@ public partial class LightlessHub
{
_logger.LogCallInfo(LightlessHubLogger.Args(dto));
var cancellationToken = RequestAbortedToken;
if (!string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) throw new HubException("Cannot modify profile data for anyone but yourself");
var existingData = await DbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == dto.User.UID, cancellationToken: RequestAbortedToken).ConfigureAwait(false);
if (existingData?.FlaggedForReport ?? false)
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile is currently flagged for report and cannot be edited").ConfigureAwait(false);
return;
}
if (existingData?.ProfileDisabled ?? false)
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile was permanently disabled and cannot be edited").ConfigureAwait(false);
return;
}
//Image Check of size/format
if (!string.IsNullOrEmpty(dto.ProfilePictureBase64))
{
byte[] imageData = Convert.FromBase64String(dto.ProfilePictureBase64);
using MemoryStream ms = new(imageData);
var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false);
if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase))
byte[] imageData;
try
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is not in PNG format").ConfigureAwait(false);
imageData = Convert.FromBase64String(dto.ProfilePictureBase64);
}
catch (FormatException)
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "The provided image is not a valid Base64 string.").ConfigureAwait(false);
return;
}
using var image = Image.Load<Rgba32>(imageData);
if (image.Width > 256 || image.Height > 256 || (imageData.Length > 250 * 1024))
MemoryStream ms = new(imageData);
await using (ms.ConfigureAwait(false))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is larger than 256x256 or more than 250KiB.").ConfigureAwait(false);
return;
var format = await Image.DetectFormatAsync(ms, RequestAbortedToken).ConfigureAwait(false);
if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is not in PNG format").ConfigureAwait(false);
return;
}
using var image = Image.Load<Rgba32>(imageData);
if (image.Width > 512 || image.Height > 512 || (imageData.Length > 2000 * 1024))
{
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is larger than 512x512 or more than 2MiB.").ConfigureAwait(false);
return;
}
}
}
if (existingData != null)
{
if (string.Equals("", dto.ProfilePictureBase64, StringComparison.OrdinalIgnoreCase))
if (existingData.FlaggedForReport)
{
existingData.Base64ProfileImage = null;
}
else if (dto.ProfilePictureBase64 != null)
{
existingData.Base64ProfileImage = dto.ProfilePictureBase64;
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile is currently flagged for report and cannot be edited").ConfigureAwait(false);
return;
}
if (dto.IsNSFW != null)
if (existingData.ProfileDisabled)
{
existingData.IsNSFW = dto.IsNSFW.Value;
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile was permanently disabled and cannot be edited").ConfigureAwait(false);
return;
}
if (dto.Description != null)
{
existingData.UserDescription = dto.Description;
}
existingData.UpdateProfileFromDto(dto);
}
else
{
UserProfileData userProfileData = new()
UserProfileData newUserProfileData = new()
{
UserUID = dto.User.UID,
Base64ProfileImage = dto.ProfilePictureBase64 ?? null,
UserDescription = dto.Description ?? null,
IsNSFW = dto.IsNSFW ?? false
IsNSFW = dto.IsNSFW ?? false,
};
await DbContext.UserProfileData.AddAsync(userProfileData).ConfigureAwait(false);
newUserProfileData.UpdateProfileFromDto(dto);
await DbContext.UserProfileData.AddAsync(newUserProfileData, cancellationToken).ConfigureAwait(false);
}
await DbContext.SaveChangesAsync().ConfigureAwait(false);
await DbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false);

View File

@@ -7,7 +7,9 @@ using LightlessSyncShared.Services;
using LightlessSyncShared.Utils.Configuration;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Core.Abstractions;
using static LightlessSyncServer.Hubs.LightlessHub;
namespace LightlessSyncServer.Services;
@@ -52,6 +54,13 @@ public sealed class SystemInfoService : BackgroundService
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableIOWorkerThreads, ioThreads);
var onlineUsers = (_redis.SearchKeysAsync("UID:*").GetAwaiter().GetResult()).Count();
var allLightfinderKeys = _redis.SearchKeysAsync("broadcast:*").GetAwaiter().GetResult().Where(c => !c.Contains("owner", StringComparison.Ordinal)).ToHashSet(StringComparer.Ordinal);
var allLightfinderItems = _redis.GetAllAsync<BroadcastRedisEntry>(allLightfinderKeys).GetAwaiter().GetResult();
var countLightFinderUsers = allLightfinderItems.Count;
var countLightFinderSyncshells = allLightfinderItems.Count(static l => !string.IsNullOrEmpty(l.Value.GID));
SystemInfoDto = new SystemInfoDto()
{
OnlineUsers = onlineUsers,
@@ -66,10 +75,12 @@ public sealed class SystemInfoService : BackgroundService
using var db = await _dbContextFactory.CreateDbContextAsync(ct).ConfigureAwait(false);
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, onlineUsers);
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeLightFinderConnections, countLightFinderUsers);
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugePairs, db.ClientPairs.AsNoTracking().Count());
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, db.Permissions.AsNoTracking().Where(p => p.IsPaused).Count());
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, db.Permissions.AsNoTracking().Count(p => p.IsPaused));
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeGroups, db.Groups.AsNoTracking().Count());
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeGroupPairs, db.GroupPairs.AsNoTracking().Count());
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeLightFinderGroups, countLightFinderSyncshells);
_lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeUsersRegistered, db.Users.AsNoTracking().Count());
}

View File

@@ -295,6 +295,8 @@ public class Startup
}, new List<string>
{
MetricsAPI.GaugeAuthorizedConnections,
MetricsAPI.GaugeLightFinderConnections,
MetricsAPI.GaugeLightFinderGroups,
MetricsAPI.GaugeConnections,
MetricsAPI.GaugePairs,
MetricsAPI.GaugePairsPaused,

View File

@@ -1,6 +1,8 @@
using LightlessSync.API.Data;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User;
using LightlessSyncShared.Models;
using static LightlessSyncServer.Hubs.LightlessHub;
@@ -8,18 +10,85 @@ namespace LightlessSyncServer.Utils;
public static class Extensions
{
public static void UpdateProfileFromDto(this GroupProfile profile, GroupProfileDto dto)
{
if (profile == null || dto == null) return;
profile.Base64GroupProfileImage = string.IsNullOrWhiteSpace(dto.PictureBase64) ? null : dto.PictureBase64;
if (dto.Tags != null) profile.Tags = dto.Tags;
if (dto.Description != null) profile.Description = dto.Description;
if (dto.IsNsfw.HasValue) profile.IsNSFW = dto.IsNsfw.Value;
}
public static void UpdateProfileFromDto(this UserProfileData profile, UserProfileDto dto)
{
if (profile == null || dto == null) return;
profile.Base64ProfileImage = string.IsNullOrWhiteSpace(dto.ProfilePictureBase64) ? null : dto.ProfilePictureBase64;
if (dto.Tags != null) profile.Tags = dto.Tags;
if (dto.Description != null) profile.UserDescription = dto.Description;
if (dto.IsNSFW.HasValue) profile.IsNSFW = dto.IsNSFW.Value;
}
public static GroupProfileDto ToDTO(this GroupProfile groupProfile)
{
if (groupProfile == null)
{
return new GroupProfileDto(Group: null, Description: null, Tags: null, PictureBase64: null, IsNsfw: false, IsDisabled: false);
}
var groupData = groupProfile.Group?.ToGroupData();
return new GroupProfileDto(
groupData,
groupProfile.Description,
groupProfile.Tags,
groupProfile.Base64GroupProfileImage,
groupProfile.IsNSFW,
groupProfile.ProfileDisabled
);
}
public static UserProfileDto ToDTO(this UserProfileData userProfileData)
{
if (userProfileData == null)
{
return new UserProfileDto(User: null, Disabled: false, IsNSFW: null, ProfilePictureBase64: null, Description: null, Tags: []);
}
var userData = userProfileData.User?.ToUserData();
return new UserProfileDto(
userData,
userProfileData.ProfileDisabled,
userProfileData.IsNSFW,
userProfileData.Base64ProfileImage,
userProfileData.UserDescription,
userProfileData.Tags
);
}
public static GroupData ToGroupData(this Group group)
{
if (group == null)
return null;
return new GroupData(group.GID, group.Alias, group.CreatedDate);
}
public static UserData ToUserData(this GroupPair pair)
{
if (pair == null)
return null;
return new UserData(pair.GroupUser.UID, pair.GroupUser.Alias);
}
public static UserData ToUserData(this User user)
{
if (user == null)
return null;
return new UserData(user.UID, user.Alias);
}

View File

@@ -9,6 +9,8 @@ public class MetricsAPI
public const string GaugeAvailableIOWorkerThreads = "lightless_available_threadpool_io";
public const string GaugeUsersRegistered = "lightless_users_registered";
public const string CounterUsersRegisteredDeleted = "lightless_users_registered_deleted";
public const string GaugeLightFinderConnections = "lightless_lightfinder_connections";
public const string GaugeLightFinderGroups = "lightless_lightfinder_groups";
public const string GaugePairs = "lightless_pairs";
public const string GaugePairsPaused = "lightless_pairs_paused";
public const string GaugeFilesTotal = "lightless_files";

View File

@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LightlessSyncServer.Migrations
{
/// <inheritdoc />
public partial class AddGroupDisabledAndNSFW : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "is_nsfw",
table: "group_profiles",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "profile_disabled",
table: "group_profiles",
type: "boolean",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "is_nsfw",
table: "group_profiles");
migrationBuilder.DropColumn(
name: "profile_disabled",
table: "group_profiles");
}
}
}

View File

@@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LightlessSyncServer.Migrations
{
/// <inheritdoc />
public partial class AddAndChangeTagsUserGroupProfile : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int[]>(
name: "tags",
table: "user_profile_data",
type: "integer[]",
nullable: true);
migrationBuilder.Sql("UPDATE group_profiles SET tags = NULL;");
migrationBuilder.AlterColumn<int[]>(
name: "tags",
table: "group_profiles",
type: "integer[]",
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "tags",
table: "user_profile_data");
migrationBuilder.AlterColumn<string>(
name: "tags",
table: "group_profiles",
type: "text",
nullable: true,
oldClrType: typeof(int[]),
oldType: "integer[]",
oldNullable: true);
}
}
}

View File

@@ -597,8 +597,16 @@ namespace LightlessSyncServer.Migrations
.HasColumnType("text")
.HasColumnName("description");
b.Property<string>("Tags")
.HasColumnType("text")
b.Property<bool>("IsNSFW")
.HasColumnType("boolean")
.HasColumnName("is_nsfw");
b.Property<bool>("ProfileDisabled")
.HasColumnType("boolean")
.HasColumnName("profile_disabled");
b.PrimitiveCollection<int[]>("Tags")
.HasColumnType("integer[]")
.HasColumnName("tags");
b.HasKey("GroupGID")
@@ -832,6 +840,10 @@ namespace LightlessSyncServer.Migrations
.HasColumnType("boolean")
.HasColumnName("profile_disabled");
b.PrimitiveCollection<int[]>("Tags")
.HasColumnType("integer[]")
.HasColumnName("tags");
b.Property<string>("UserDescription")
.HasColumnType("text")
.HasColumnName("user_description");

View File

@@ -13,6 +13,8 @@ public class GroupProfile
public string GroupGID { get; set; }
public Group Group { get; set; }
public string Description { get; set; }
public string Tags { get; set; }
public int[] Tags { get; set; }
public string Base64GroupProfileImage { get; set; }
public bool IsNSFW { get; set; } = false;
public bool ProfileDisabled { get; set; } = false;
}

View File

@@ -12,6 +12,7 @@ public class UserProfileData
public User User { get; set; }
public string UserDescription { get; set; }
public int[] Tags { get; set; }
[Required]
[Key]