From 47005d10e9ffa9ef7732a84ab1badfe11b8e57d3 Mon Sep 17 00:00:00 2001 From: Karim Kimsanbaev Date: Mon, 24 Jun 2024 15:21:22 +0300 Subject: [PATCH 1/2] Implement get account & check password & remove closure allocations --- .../Scripts/AccountsDatabaseAccessor.cs | 99 +++++++------------ 1 file changed, 34 insertions(+), 65 deletions(-) diff --git a/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/AccountsDatabaseAccessor.cs b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/AccountsDatabaseAccessor.cs index 4478cdd7..a86cf85a 100644 --- a/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/AccountsDatabaseAccessor.cs +++ b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/AccountsDatabaseAccessor.cs @@ -63,67 +63,46 @@ public IAccountInfoData CreateAccountInstance() public async Task GetAccountByIdAsync(string id) { var filter = Builders.Filter.Eq(e => e.Id, id); - return await Task.Run(() => - { - return accountsCollection.Find(filter).FirstOrDefault(); - }); + return await accountsCollection.Find(filter).FirstOrDefaultAsync(); } public async Task GetAccountByUsernameAsync(string username) { var filter = Builders.Filter.Eq(e => e.Username, username); - return await Task.Run(() => - { - return accountsCollection.Find(filter).FirstOrDefault(); - }); + return await accountsCollection.Find(filter).FirstOrDefaultAsync(); } public async Task GetAccountByEmailAsync(string email) { var filter = Builders.Filter.Eq(e => e.Email, email); - return await Task.Run(() => - { - return accountsCollection.Find(filter).FirstOrDefault(); - }); + return await accountsCollection.Find(filter).FirstOrDefaultAsync(); } public async Task GetAccountByExtraPropertyAsync(string phoneNumber) { var filter = Builders.Filter.Eq(e => e.PhoneNumber, phoneNumber); - return await Task.Run(() => - { - return accountsCollection.Find(filter).FirstOrDefault(); - }); + return await accountsCollection.Find(filter).FirstOrDefaultAsync(); } public async Task GetAccountByTokenAsync(string token) { var filter = Builders.Filter.Eq(e => e.Token, token); - return await Task.Run(() => - { - return accountsCollection.Find(filter).FirstOrDefault(); - }); + return await accountsCollection.Find(filter).FirstOrDefaultAsync(); } public async Task GetAccountByDeviceIdAsync(string deviceId) { var filter = Builders.Filter.Eq(e => e.DeviceId, deviceId.ToLower()); - return await Task.Run(() => - { - return accountsCollection.Find(filter).FirstOrDefault(); - }); + return await accountsCollection.Find(filter).FirstOrDefaultAsync(); } public async Task SavePasswordResetCodeAsync(string email, string code) { - await Task.Run(() => + await resetCodesCollection.DeleteManyAsync(i => i.Email == email.ToLower()); + await resetCodesCollection.InsertOneAsync(new PasswordResetDataMongoDB() { - resetCodesCollection.DeleteMany(i => i.Email == email.ToLower()); - resetCodesCollection.InsertOne(new PasswordResetDataMongoDB() - { - Email = email.ToLower(), - Code = code - }); + Email = email.ToLower(), + Code = code }); } @@ -132,25 +111,18 @@ public async Task CheckPasswordResetCodeAsync(string email) PasswordResetDataMongoDB data = default; var filter = Builders.Filter.Eq(i => i.Email, email.ToLower()); - - await Task.Run(() => - { - data = resetCodesCollection.Find(filter).FirstOrDefault(); - }); + data = await resetCodesCollection.Find(filter).FirstOrDefaultAsync(); return data != null ? data.Code : ""; } public async Task SaveEmailConfirmationCodeAsync(string email, string code) { - await Task.Run(() => + await emailConfirmations.DeleteManyAsync(i => i.Email == email.ToLower()); + await emailConfirmations.InsertOneAsync(new EmailConfirmationDataMongoDB() { - emailConfirmations.DeleteMany(i => i.Email == email.ToLower()); - emailConfirmations.InsertOne(new EmailConfirmationDataMongoDB() - { - Code = code, - Email = email - }); + Code = code, + Email = email }); } @@ -159,8 +131,7 @@ public async Task CheckEmailConfirmationCodeAsync(string email, string cod if (string.IsNullOrEmpty(email)) return false; if (string.IsNullOrEmpty(code)) return false; - EmailConfirmationDataMongoDB entry = await emailConfirmations.Find(i => i.Email == email).FirstAsync(); - + var entry = await emailConfirmations.Find(i => i.Email == email).FirstOrDefaultAsync(); if (entry != null && entry.Code == code) { await emailConfirmations.DeleteOneAsync(i => i.Email == email.ToLower()); @@ -173,21 +144,13 @@ public async Task CheckEmailConfirmationCodeAsync(string email, string cod public async Task UpdateAccountAsync(IAccountInfoData account) { var filter = Builders.Filter.Eq(e => e.Id, account.Id); - - await Task.Run(() => - { - accountsCollection.ReplaceOne(filter, account as AccountInfoMongoDB); - }); + await accountsCollection.ReplaceOneAsync(filter, account as AccountInfoMongoDB); } public async Task InsertAccountAsync(IAccountInfoData account) { var acc = account as AccountInfoMongoDB; - await Task.Run(() => - { - accountsCollection.InsertOne(acc); - }); - + await accountsCollection.InsertOneAsync(acc); return acc.Id; } @@ -195,12 +158,8 @@ public async Task InsertOrUpdateTokenAsync(IAccountInfoData account, string toke { var filter = Builders.Filter.Eq(e => e.Id, account.Id); var update = Builders.Update.Set(e => e.Token, token); - - await Task.Run(() => - { - account.Token = token; - accountsCollection.UpdateOne(filter, update); - }); + account.Token = token; + await accountsCollection.UpdateOneAsync(filter, update); } public Task GetPhoneNumberConfirmationCodeAsync(string phoneNumber) @@ -220,9 +179,11 @@ public Task GetAccountByPropertyAsync(string propertyKey, stri throw new NotImplementedException(); } - public Task GetAccountByExtraPropertyAsync(string propertyKey, string propertyValue) + public async Task GetAccountByExtraPropertyAsync(string propertyKey, string propertyValue) { - throw new NotImplementedException(); + // TODO: Very slowly. Need to create index for each extra property like LiteDB. + var account = await accountsCollection.Find(e => e.ExtraProperties[propertyKey] == propertyValue).SingleOrDefaultAsync(); + return account; } public Task InsertOrUpdateExtraProperties(string accountId, Dictionary properties) @@ -235,9 +196,17 @@ public Task> GetExtraPropertiesAsync(string accountId throw new NotImplementedException(); } - public Task CheckPasswordResetCodeAsync(string email, string code) + public async Task CheckPasswordResetCodeAsync(string email, string code) { - throw new NotImplementedException(); + var filter = Builders.Filter.Eq(i => i.Email, email.ToLower()); + var entry = await resetCodesCollection.Find(filter).FirstOrDefaultAsync(); + if (entry != null && entry.Code == code) + { + await resetCodesCollection.DeleteManyAsync(filter); + return true; + } + + return false; } } } From 5f0be8402b95657c384a2a1d49feead3bee902af Mon Sep 17 00:00:00 2001 From: Karim Kimsanbaev Date: Mon, 24 Jun 2024 15:49:47 +0300 Subject: [PATCH 2/2] Extract extra properties to collection for perfomance --- .../Scripts/AccountsDatabaseAccessor.cs | 102 +++++++++++++++--- .../Scripts/Models/AccountInfoMongoDB.cs | 1 + .../Models/ExtraPropertyDataMongoDB.cs | 17 +++ .../Models/ExtraPropertyDataMongoDB.cs.meta | 3 + 4 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/ExtraPropertyDataMongoDB.cs create mode 100644 Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/ExtraPropertyDataMongoDB.cs.meta diff --git a/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/AccountsDatabaseAccessor.cs b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/AccountsDatabaseAccessor.cs index a86cf85a..d9fdeff6 100644 --- a/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/AccountsDatabaseAccessor.cs +++ b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/AccountsDatabaseAccessor.cs @@ -15,6 +15,7 @@ public class AccountsDatabaseAccessor : IAccountsDatabaseAccessor private readonly IMongoDatabase database; private readonly IMongoCollection accountsCollection; + private readonly IMongoCollection extraPropertiesCollection; private readonly IMongoCollection resetCodesCollection; private readonly IMongoCollection emailConfirmations; @@ -30,6 +31,7 @@ public AccountsDatabaseAccessor(MongoClient client, string databaseName) database = this.client.GetDatabase(databaseName); accountsCollection = database.GetCollection("accounts"); + extraPropertiesCollection = database.GetCollection("extraProperties"); resetCodesCollection = database.GetCollection("resetCodes"); emailConfirmations = database.GetCollection("emailConfirmationCodes"); @@ -38,7 +40,16 @@ public AccountsDatabaseAccessor(MongoClient client, string databaseName) accountsCollection.Indexes.CreateOne( new CreateIndexModel( - Builders.IndexKeys.Ascending(e => e.Username), new CreateIndexOptions() { Unique = true } + Builders.IndexKeys.Ascending(e => e.Username), new CreateIndexOptions() {Unique = true} + ) + ); + + extraPropertiesCollection.Indexes.CreateOne( + new CreateIndexModel( + Builders.IndexKeys + .Ascending(e => e.PropertyKey) + .Ascending(e => e.PropertyValue), + new CreateIndexOptions {Unique = true} ) ); @@ -63,19 +74,43 @@ public IAccountInfoData CreateAccountInstance() public async Task GetAccountByIdAsync(string id) { var filter = Builders.Filter.Eq(e => e.Id, id); - return await accountsCollection.Find(filter).FirstOrDefaultAsync(); + var account = await accountsCollection.Find(filter).FirstOrDefaultAsync(); + if (account != null) + { + account.LastLogin = DateTime.UtcNow; + await accountsCollection.ReplaceOneAsync(Builders.Filter.Eq(e => e.Id, account.Id), account); + account.ExtraProperties = await GetExtraPropertiesAsync(account.Id); + } + + return account; } public async Task GetAccountByUsernameAsync(string username) { var filter = Builders.Filter.Eq(e => e.Username, username); - return await accountsCollection.Find(filter).FirstOrDefaultAsync(); + var account = await accountsCollection.Find(filter).FirstOrDefaultAsync(); + if (account != null) + { + account.LastLogin = DateTime.UtcNow; + await accountsCollection.ReplaceOneAsync(Builders.Filter.Eq(e => e.Id, account.Id), account); + account.ExtraProperties = await GetExtraPropertiesAsync(account.Id); + } + + return account; } public async Task GetAccountByEmailAsync(string email) { var filter = Builders.Filter.Eq(e => e.Email, email); - return await accountsCollection.Find(filter).FirstOrDefaultAsync(); + var account = await accountsCollection.Find(filter).FirstOrDefaultAsync(); + if (account != null) + { + account.LastLogin = DateTime.UtcNow; + await accountsCollection.ReplaceOneAsync(Builders.Filter.Eq(e => e.Id, account.Id), account); + account.ExtraProperties = await GetExtraPropertiesAsync(account.Id); + } + + return account; } public async Task GetAccountByExtraPropertyAsync(string phoneNumber) @@ -87,13 +122,29 @@ public async Task GetAccountByExtraPropertyAsync(string phoneN public async Task GetAccountByTokenAsync(string token) { var filter = Builders.Filter.Eq(e => e.Token, token); - return await accountsCollection.Find(filter).FirstOrDefaultAsync(); + var account = await accountsCollection.Find(filter).FirstOrDefaultAsync(); + if (account != null) + { + account.LastLogin = DateTime.UtcNow; + await accountsCollection.ReplaceOneAsync(Builders.Filter.Eq(e => e.Id, account.Id), account); + account.ExtraProperties = await GetExtraPropertiesAsync(account.Id); + } + + return account; } public async Task GetAccountByDeviceIdAsync(string deviceId) { var filter = Builders.Filter.Eq(e => e.DeviceId, deviceId.ToLower()); - return await accountsCollection.Find(filter).FirstOrDefaultAsync(); + var account = await accountsCollection.Find(filter).FirstOrDefaultAsync(); + if (account != null) + { + account.LastLogin = DateTime.UtcNow; + await accountsCollection.ReplaceOneAsync(Builders.Filter.Eq(e => e.Id, account.Id), account); + account.ExtraProperties = await GetExtraPropertiesAsync(account.Id); + } + + return account; } public async Task SavePasswordResetCodeAsync(string email, string code) @@ -145,12 +196,14 @@ public async Task UpdateAccountAsync(IAccountInfoData account) { var filter = Builders.Filter.Eq(e => e.Id, account.Id); await accountsCollection.ReplaceOneAsync(filter, account as AccountInfoMongoDB); + await InsertOrUpdateExtraProperties(account.Id, account.ExtraProperties); } public async Task InsertAccountAsync(IAccountInfoData account) { var acc = account as AccountInfoMongoDB; await accountsCollection.InsertOneAsync(acc); + await InsertOrUpdateExtraProperties(acc.Id, account.ExtraProperties); return acc.Id; } @@ -181,19 +234,42 @@ public Task GetAccountByPropertyAsync(string propertyKey, stri public async Task GetAccountByExtraPropertyAsync(string propertyKey, string propertyValue) { - // TODO: Very slowly. Need to create index for each extra property like LiteDB. - var account = await accountsCollection.Find(e => e.ExtraProperties[propertyKey] == propertyValue).SingleOrDefaultAsync(); - return account; + var filter = Builders.Filter.And( + Builders.Filter.Eq(e => e.PropertyKey, propertyKey), + Builders.Filter.Eq(e => e.PropertyValue, propertyValue) + ); + var extraProperty = await extraPropertiesCollection.Find(filter).FirstOrDefaultAsync(); + if (extraProperty == null) + { + return null; + } + + var accountFilter = Builders.Filter.Eq(e => e.Id, extraProperty.AccountId); + return await accountsCollection.Find(accountFilter).FirstOrDefaultAsync(); } - public Task InsertOrUpdateExtraProperties(string accountId, Dictionary properties) + private async Task InsertOrUpdateExtraProperties(string accountId, Dictionary properties) { - throw new NotImplementedException(); + foreach (var (key, value) in properties) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq(e => e.AccountId, accountId), + Builders.Filter.Eq(e => e.PropertyKey, key) + ); + + var update = Builders.Update.Set(e => e.PropertyValue, value); + + var options = new UpdateOptions {IsUpsert = true}; + + await extraPropertiesCollection.UpdateOneAsync(filter, update, options); + } } - public Task> GetExtraPropertiesAsync(string accountId) + public async Task> GetExtraPropertiesAsync(string accountId) { - throw new NotImplementedException(); + var filter = Builders.Filter.Eq(e => e.AccountId, accountId); + var extraProperties = await extraPropertiesCollection.Find(filter).ToListAsync(); + return extraProperties.ToDictionary(e => e.PropertyKey, e => e.PropertyValue); } public async Task CheckPasswordResetCodeAsync(string email, string code) diff --git a/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/AccountInfoMongoDB.cs b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/AccountInfoMongoDB.cs index 6cdeea3f..32e52bd1 100644 --- a/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/AccountInfoMongoDB.cs +++ b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/AccountInfoMongoDB.cs @@ -26,6 +26,7 @@ public class AccountInfoMongoDB : IAccountInfoData public bool IsBanned { get; set; } public string DeviceId { get; set; } public string DeviceName { get; set; } + [BsonIgnore] public Dictionary ExtraProperties { get; set; } public event Action OnChangedEvent; diff --git a/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/ExtraPropertyDataMongoDB.cs b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/ExtraPropertyDataMongoDB.cs new file mode 100644 index 00000000..ff6e77ef --- /dev/null +++ b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/ExtraPropertyDataMongoDB.cs @@ -0,0 +1,17 @@ +#if (!UNITY_WEBGL && !UNITY_IOS) || UNITY_EDITOR +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace MasterServerToolkit.Bridges.MongoDB +{ + public class ExtraPropertyDataMongoDB + { + [BsonId] + public ObjectId _id { get; set; } + public string Id { get => _id.ToString(); set => _id = new ObjectId(value); } + public string AccountId { get; set; } + public string PropertyKey { get; set; } + public string PropertyValue { get; set; } + } +} +#endif \ No newline at end of file diff --git a/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/ExtraPropertyDataMongoDB.cs.meta b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/ExtraPropertyDataMongoDB.cs.meta new file mode 100644 index 00000000..ec1aeb31 --- /dev/null +++ b/Assets/MasterServerToolkit/Bridges/MongoDB/Scripts/Models/ExtraPropertyDataMongoDB.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 04a4d313165249e19975b2783483ad8a +timeCreated: 1719231828 \ No newline at end of file