Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 71 additions & 54 deletions GenOnlineService/Controllers/Friends/SocialController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public override Type GetReturnType()
[Route("env/{environment}/contract/{contract_version}/[controller]")]
public class SocialController : ControllerBase
{
private const int FriendsLimit = 200;

private readonly IDbContextFactory<AppDbContext> _dbFactory;
private readonly ILogger<SocialController> _logger;

Expand All @@ -81,18 +83,47 @@ public SocialController(IDbContextFactory<AppDbContext> dbFactory, ILogger<Socia
// POST = accept request
// DELETE = reject request

private async Task HelperFunction_AcceptFriendRequest(Int64 source_user_id, Int64 target_user_id)
private bool RejectIfFriendsListFull(Int64 user_id, SharedUserData userData)
{
if (userData.GetSocialContainer().Friends.Count < FriendsLimit)
{
return false;
}

WebSocketMessage_Social_FriendsListFull friendsListFullEvent = new();
friendsListFullEvent.msg_id = (int)EWebSocketMessageID.SOCIAL_CANT_ADD_FRIEND_LIST_FULL;
byte[] bytesJSON = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(friendsListFullEvent));

WebsocketHelper.SendToAllSessionsOfUser(user_id, bytesJSON);
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return true;
}

private async Task<bool> HelperFunction_AcceptFriendRequest(Int64 source_user_id, Int64 target_user_id)
{
// NOTE: target user does NOT need to be signed in
SharedUserData? sharedUserDataSource = GenOnlineService.WebSocketManager.GetSharedDataForUser(source_user_id);
SharedUserData? sharedUserDataTarget = GenOnlineService.WebSocketManager.GetSharedDataForUser(target_user_id);

if (sharedUserDataSource == null)
{
return false;
}

await using var db = await _dbFactory.CreateDbContextAsync();
// NOTE: target user does NOT need to be signed in

// a friendship adds an entry to both lists, so the target must have room too
int targetFriendCount = sharedUserDataTarget != null
? sharedUserDataTarget.GetSocialContainer().Friends.Count
: await Database.Social.CountFriends(db, target_user_id);

if (targetFriendCount >= FriendsLimit)
{
return false;
}

// remove the request from requestor (online version)
#pragma warning disable CS8602 // Dereference of a possibly null reference.
sharedUserDataSource.GetSocialContainer().PendingRequests.Remove(target_user_id);
#pragma warning restore CS8602 // Dereference of a possibly null reference.

// remove the request from requestor (db)
await Database.Social.RemovePendingFriendRequest(db, source_user_id, target_user_id);
Expand Down Expand Up @@ -141,6 +172,8 @@ private async Task HelperFunction_AcceptFriendRequest(Int64 source_user_id, Int6
// send to all sessions
WebsocketHelper.SendToAllSessionsOfUser(target_user_id, bytesJSON);
}

return true;
}

// Accept a request
Expand All @@ -150,13 +183,23 @@ public async Task AcceptPendingRequest(Int64 target_user_id)
{
// source user must be signed in (anywhere)
Int64 source_user_id = TokenHelper.GetUserID(this);
if (source_user_id == -1 || WebSocketManager.GetSharedDataForUser(source_user_id) == null)
SharedUserData? userData = WebSocketManager.GetSharedDataForUser(source_user_id);
if (source_user_id == -1 || userData == null)
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}

HelperFunction_AcceptFriendRequest(source_user_id, target_user_id);
if (RejectIfFriendsListFull(source_user_id, userData))
{
return;
}

if (!await HelperFunction_AcceptFriendRequest(source_user_id, target_user_id))
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}

SocialHelper.NotifyFriendslistDirty(source_user_id);
SocialHelper.NotifyFriendslistDirty(target_user_id);
Expand All @@ -169,17 +212,15 @@ public async Task RejectPendingRequest(Int64 target_user_id)
{
// source user must be signed in
Int64 source_user_id = TokenHelper.GetUserID(this);
if (source_user_id == -1 || WebSocketManager.GetSharedDataForUser(source_user_id) == null)
SharedUserData? userData = WebSocketManager.GetSharedDataForUser(source_user_id);
if (source_user_id == -1 || userData == null)
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}

// remove the request from requestor (online version)
#pragma warning disable CS8602 // Dereference of a possibly null reference.
SharedUserData? userData = WebSocketManager.GetSharedDataForUser(source_user_id);
userData.GetSocialContainer().PendingRequests.Remove(target_user_id);
#pragma warning restore CS8602 // Dereference of a possibly null reference.

// remove the request from requestor (db)
// NOTE: Target and source are inverted here because the target is actually the person who sent the request, source is the person taking action on the friend request
Expand All @@ -197,21 +238,19 @@ public async Task RemoveFriend(Int64 target_user_id)
{
// source user must be signed in
Int64 source_user_id = TokenHelper.GetUserID(this);
if (source_user_id == -1 || WebSocketManager.GetSharedDataForUser(source_user_id) == null)
SharedUserData? userData = WebSocketManager.GetSharedDataForUser(source_user_id);
if (source_user_id == -1 || userData == null)
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}

// must be friends
#pragma warning disable CS8602 // Dereference of a possibly null reference.
SharedUserData? userData = WebSocketManager.GetSharedDataForUser(source_user_id);
if (!userData.GetSocialContainer().Friends.Contains(target_user_id))
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
#pragma warning restore CS8602 // Dereference of a possibly null reference.

// remove the request from requestor (online version)
userData.GetSocialContainer().Friends.Remove(target_user_id);
Expand Down Expand Up @@ -239,36 +278,24 @@ public async Task AddFriend(Int64 target_user_id)
{
// source user must be signed in
Int64 requester_user_id = TokenHelper.GetUserID(this);
if (requester_user_id == -1 || WebSocketManager.GetSharedDataForUser(requester_user_id) == null)
SharedUserData? userData = WebSocketManager.GetSharedDataForUser(requester_user_id);
if (requester_user_id == -1 || userData == null)
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}

// too many friends?
const int friendsLimit = 200;
SharedUserData? userData = WebSocketManager.GetSharedDataForUser(requester_user_id);
if (userData.GetSocialContainer().Friends.Count >= friendsLimit)
if (RejectIfFriendsListFull(requester_user_id, userData))
{
if (userData != null)
{
WebSocketMessage_Social_FriendsListFull friendsListFullEvent = new();
friendsListFullEvent.msg_id = (int)EWebSocketMessageID.SOCIAL_CANT_ADD_FRIEND_LIST_FULL;
byte[] bytesJSON = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(friendsListFullEvent));

// send to all sessions
WebsocketHelper.SendToAllSessionsOfUser(requester_user_id, bytesJSON);
}
}
return;
}

// Check not already friends
#pragma warning disable CS8602 // Dereference of a possibly null reference.
if (userData.GetSocialContainer().Friends.Contains(target_user_id))
{
Response.StatusCode = (int)HttpStatusCode.Conflict;
return;
}
#pragma warning restore CS8602 // Dereference of a possibly null reference.

await using var db = await _dbFactory.CreateDbContextAsync();

Expand Down Expand Up @@ -309,7 +336,11 @@ public async Task AddFriend(Int64 target_user_id)
if (userData.GetSocialContainer().PendingRequests.Contains(target_user_id))
{
// accept their request
HelperFunction_AcceptFriendRequest(requester_user_id, target_user_id);
if (!await HelperFunction_AcceptFriendRequest(requester_user_id, target_user_id))
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
}
else
{
Expand Down Expand Up @@ -341,18 +372,14 @@ public async Task<APIResult> Get_FriendsAndRequests()

// source user must be signed in
Int64 requester_user_id = TokenHelper.GetUserID(this);
if (requester_user_id == -1 || WebSocketManager.GetSharedDataForUser(requester_user_id) == null)
SharedUserData? sourceData = WebSocketManager.GetSharedDataForUser(requester_user_id);
if (requester_user_id == -1 || sourceData == null)
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return result;
}

// get websockets & data
SharedUserData? sourceData = WebSocketManager.GetSharedDataForUser(requester_user_id);

#pragma warning disable CS8602 // Dereference of a possibly null reference.
HashSet<Int64> setFriends = sourceData.GetSocialContainer().Friends;
#pragma warning restore CS8602 // Dereference of a possibly null reference.
HashSet<Int64> setPendingRequests = sourceData.GetSocialContainer().PendingRequests;

List<Int64> lstCombined = new List<Int64>();
Expand Down Expand Up @@ -437,17 +464,14 @@ public async Task<APIResult> Get_Blocked()

// source user must be signed in
Int64 requester_user_id = TokenHelper.GetUserID(this);
if (requester_user_id == -1 || WebSocketManager.GetSharedDataForUser(requester_user_id) == null)
SharedUserData? sourceData = WebSocketManager.GetSharedDataForUser(requester_user_id);
if (requester_user_id == -1 || sourceData == null)
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return result;
}

SharedUserData? sourceData = WebSocketManager.GetSharedDataForUser(requester_user_id);

#pragma warning disable CS8602 // Dereference of a possibly null reference.
HashSet<Int64> setBlocked = sourceData.GetSocialContainer().Blocked;
#pragma warning restore CS8602 // Dereference of a possibly null reference.

await using var db = await _dbFactory.CreateDbContextAsync();
Dictionary<Int64, string> dictDisplayNames = await Database.Users.GetDisplayNameBulk(db, setBlocked.ToList());
Expand Down Expand Up @@ -500,16 +524,14 @@ public async Task Add_Block(Int64 target_user_id)
{
// source user must be signed in
Int64 requester_user_id = TokenHelper.GetUserID(this);
if (requester_user_id == -1 || WebSocketManager.GetSharedDataForUser(requester_user_id) == null)
SharedUserData? sourceData = WebSocketManager.GetSharedDataForUser(requester_user_id);
if (requester_user_id == -1 || sourceData == null)
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}

// Check not already blocked
SharedUserData? sourceData = WebSocketManager.GetSharedDataForUser(requester_user_id);

#pragma warning disable CS8602 // Dereference of a possibly null reference.
if (sourceData.GetSocialContainer().Blocked.Contains(target_user_id))
{
Response.StatusCode = (int)HttpStatusCode.Conflict;
Expand All @@ -527,8 +549,6 @@ public async Task Add_Block(Int64 target_user_id)
}
}

#pragma warning restore CS8602 // Dereference of a possibly null reference.

// We must:
//// - Remove from source friends, DB (if present)
//// - Remove from source friends, Cache (if present)
Expand Down Expand Up @@ -573,22 +593,19 @@ public async Task Remove_Block(Int64 target_user_id)

// source user must be signed in
Int64 requester_user_id = TokenHelper.GetUserID(this);
if (requester_user_id == -1 || WebSocketManager.GetSharedDataForUser(requester_user_id) == null)
SharedUserData? sourceData = WebSocketManager.GetSharedDataForUser(requester_user_id);
if (requester_user_id == -1 || sourceData == null)
{
Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}

SharedUserData? sourceData = WebSocketManager.GetSharedDataForUser(requester_user_id);

// Check blocked
#pragma warning disable CS8602 // Dereference of a possibly null reference.
if (!sourceData.GetSocialContainer().Blocked.Contains(target_user_id))
{
Response.StatusCode = (int)HttpStatusCode.Conflict;
return;
}
#pragma warning restore CS8602 // Dereference of a possibly null reference.

// - Remove from block list (cache)
sourceData.GetSocialContainer().Blocked.Remove(target_user_id);
Expand Down
24 changes: 24 additions & 0 deletions GenOnlineService/Database/Database.Social.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ public static class Social
.Where(f => f.UserId1 == userId || f.UserId2 == userId)
);

private static readonly Func<AppDbContext, long, Task<int>> _countFriends =
EF.CompileAsyncQuery(
(AppDbContext db, long userId) =>
db.Friends
.Count(f => f.UserId1 == userId || f.UserId2 == userId)
);

private static readonly Func<AppDbContext, long, IAsyncEnumerable<long>> _getBlocked =
EF.CompileAsyncQuery(
(AppDbContext db, long userId) =>
Expand Down Expand Up @@ -139,6 +146,23 @@ public static async Task<HashSet<long>> GetFriends(AppDbContext db, long userId)
}


public static async Task<int> CountFriends(AppDbContext db, long userId)
{
try
{
return await _countFriends(db, userId);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] CountFriends failed: {ex.Message}");
SentrySdk.CaptureException(ex);

// Treat an unreadable count as full so the friends limit is never bypassed.
return int.MaxValue;
}
}


public static async Task<HashSet<long>> GetBlocked(AppDbContext db, long sourceUserId)
{
HashSet<long> result = new();
Expand Down
Loading