From b98612be7a30e58d8d09090af390f46e16b49bec Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Mon, 22 Dec 2025 20:11:43 +0500 Subject: [PATCH] sync: migrate sync devices from fs to mongodb --- .../Accessors/SyncItemsRepositoryAccessor.cs | 13 +- Notesnook.API/Constants.cs | 4 +- .../Controllers/AnnouncementController.cs | 34 +- Notesnook.API/Controllers/InboxController.cs | 4 +- .../Controllers/MonographsController.cs | 26 +- Notesnook.API/Controllers/S3Controller.cs | 31 +- .../Controllers/SyncDeviceController.cs | 10 +- Notesnook.API/Hubs/SyncV2Hub.cs | 83 +++-- .../ISyncItemsRepositoryAccessor.cs | 2 + Notesnook.API/Jobs/DeviceCleanupJob.cs | 76 ++--- Notesnook.API/Models/DeviceIdsChunk.cs | 16 + Notesnook.API/Models/MonographMetadata.cs | 5 +- Notesnook.API/Models/SyncDevice.cs | 19 ++ Notesnook.API/Program.cs | 3 +- Notesnook.API/Services/S3Service.cs | 9 +- Notesnook.API/Services/SyncDeviceService.cs | 321 ++++++++---------- Notesnook.API/Services/UserService.cs | 21 +- Notesnook.API/Startup.cs | 12 +- Notesnook.API/appsettings.Development.json | 2 +- 19 files changed, 341 insertions(+), 350 deletions(-) create mode 100644 Notesnook.API/Models/DeviceIdsChunk.cs create mode 100644 Notesnook.API/Models/SyncDevice.cs diff --git a/Notesnook.API/Accessors/SyncItemsRepositoryAccessor.cs b/Notesnook.API/Accessors/SyncItemsRepositoryAccessor.cs index cdfb34a..d672500 100644 --- a/Notesnook.API/Accessors/SyncItemsRepositoryAccessor.cs +++ b/Notesnook.API/Accessors/SyncItemsRepositoryAccessor.cs @@ -46,6 +46,8 @@ namespace Notesnook.API.Accessors public Repository Monographs { get; } public Repository InboxApiKey { get; } public Repository InboxItems { get; } + public Repository SyncDevices { get; } + public Repository DeviceIdsChunks { get; } public SyncItemsRepositoryAccessor(IDbContext dbContext, @@ -74,13 +76,20 @@ namespace Notesnook.API.Accessors [FromKeyedServices(Collections.TagsKey)] IMongoCollection tags, - Repository usersSettings, Repository monographs, - Repository inboxApiKey, Repository inboxItems, ILogger logger) + Repository usersSettings, + Repository monographs, + Repository inboxApiKey, + Repository inboxItems, + Repository syncDevices, + Repository deviceIdsChunks, + ILogger logger) { UsersSettings = usersSettings; Monographs = monographs; InboxApiKey = inboxApiKey; InboxItems = inboxItems; + SyncDevices = syncDevices; + DeviceIdsChunks = deviceIdsChunks; Notebooks = new SyncItemsRepository(dbContext, notebooks, logger); Notes = new SyncItemsRepository(dbContext, notes, logger); Contents = new SyncItemsRepository(dbContext, content, logger); diff --git a/Notesnook.API/Constants.cs b/Notesnook.API/Constants.cs index 6e6e366..c220118 100644 --- a/Notesnook.API/Constants.cs +++ b/Notesnook.API/Constants.cs @@ -14,7 +14,9 @@ namespace Notesnook.API public const string TagsKey = "tags"; public const string ColorsKey = "colors"; public const string VaultsKey = "vaults"; - public const string InboxItems = "inbox_items"; + public const string InboxItemsKey = "inbox_items"; public const string InboxApiKeysKey = "inbox_api_keys"; + public const string SyncDevicesKey = "sync_devices"; + public const string DeviceIdsChunksKey = "device_ids_chunks"; } } \ No newline at end of file diff --git a/Notesnook.API/Controllers/AnnouncementController.cs b/Notesnook.API/Controllers/AnnouncementController.cs index 2c1944d..ceb6f54 100644 --- a/Notesnook.API/Controllers/AnnouncementController.cs +++ b/Notesnook.API/Controllers/AnnouncementController.cs @@ -21,11 +21,14 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using AngleSharp.Text; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MongoDB.Driver; +using Notesnook.API.Accessors; using Notesnook.API.Models; using Streetwriters.Common; +using Streetwriters.Common.Accessors; using Streetwriters.Common.Interfaces; using Streetwriters.Common.Models; using Streetwriters.Data.Repositories; @@ -35,25 +38,26 @@ namespace Notesnook.API.Controllers // TODO: this should be moved out into its own microservice [ApiController] [Route("announcements")] - public class AnnouncementController : ControllerBase + public class AnnouncementController(Repository announcements, WampServiceAccessor serviceAccessor) : ControllerBase { - private Repository Announcements { get; set; } - public AnnouncementController(Repository announcements) - { - Announcements = announcements; - } - [HttpGet("active")] [AllowAnonymous] public async Task GetActiveAnnouncements([FromQuery] string? userId) { - var totalActive = await Announcements.Collection.CountDocumentsAsync(Builders.Filter.Eq("IsActive", true)); - if (totalActive <= 0) return Ok(Array.Empty()); - - var announcements = (await Announcements.FindAsync((a) => a.IsActive)).Where((a) => a.UserIds == null || a.UserIds.Length == 0 || a.UserIds.Contains(userId)); - foreach (var announcement in announcements) + var filter = Builders.Filter.Eq(x => x.IsActive, true); + if (!string.IsNullOrEmpty(userId)) { - if (announcement.UserIds != null && !announcement.UserIds.Contains(userId)) continue; + var userFilter = Builders.Filter.Or( + Builders.Filter.Eq(x => x.UserIds, null), + Builders.Filter.Size(x => x.UserIds, 0), + Builders.Filter.AnyEq(x => x.UserIds, userId) + ); + filter = Builders.Filter.And(filter, userFilter); + } + var userAnnouncements = await announcements.Collection.Find(filter).ToListAsync(); + foreach (var announcement in userAnnouncements) + { + if (userId != null && announcement.UserIds != null && !announcement.UserIds.Contains(userId)) continue; foreach (var item in announcement.Body) { @@ -66,13 +70,13 @@ namespace Notesnook.API.Controllers if (action.Data.Contains("{{Email}}")) { - var user = string.IsNullOrEmpty(userId) ? null : await (await WampServers.IdentityServer.GetServiceAsync(IdentityServerTopics.UserAccountServiceTopic)).GetUserAsync(Clients.Notesnook.Id, userId); + var user = string.IsNullOrEmpty(userId) ? null : await serviceAccessor.UserAccountService.GetUserAsync(Clients.Notesnook.Id, userId); action.Data = action.Data.Replace("{{Email}}", user?.Email ?? ""); } } } } - return Ok(announcements); + return Ok(userAnnouncements); } } } diff --git a/Notesnook.API/Controllers/InboxController.cs b/Notesnook.API/Controllers/InboxController.cs index 888cca3..b15c9bf 100644 --- a/Notesnook.API/Controllers/InboxController.cs +++ b/Notesnook.API/Controllers/InboxController.cs @@ -40,6 +40,7 @@ namespace Notesnook.API.Controllers Repository inboxApiKeysRepository, Repository userSettingsRepository, Repository inboxItemsRepository, + SyncDeviceService syncDeviceService, ILogger logger) : ControllerBase { @@ -182,8 +183,7 @@ namespace Notesnook.API.Controllers request.UserId = userId; request.ItemId = ObjectId.GenerateNewId().ToString(); await inboxItemsRepository.InsertAsync(request); - new SyncDeviceService(new SyncDevice(userId, string.Empty)) - .AddIdsToAllDevices([$"{request.ItemId}:inboxItems"]); + await syncDeviceService.AddIdsToAllDevicesAsync(userId, [new(request.ItemId, "inbox_item")]); await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage { OriginTokenId = null, diff --git a/Notesnook.API/Controllers/MonographsController.cs b/Notesnook.API/Controllers/MonographsController.cs index 25ec93d..7e1ff9c 100644 --- a/Notesnook.API/Controllers/MonographsController.cs +++ b/Notesnook.API/Controllers/MonographsController.cs @@ -46,7 +46,7 @@ namespace Notesnook.API.Controllers [ApiController] [Route("monographs")] [Authorize("Sync")] - public class MonographsController(Repository monographs, IURLAnalyzer analyzer, ILogger logger) : ControllerBase + public class MonographsController(Repository monographs, IURLAnalyzer analyzer, SyncDeviceService syncDeviceService, ILogger logger) : ControllerBase { const string SVG_PIXEL = ""; private const int MAX_DOC_SIZE = 15 * 1024 * 1024; @@ -317,32 +317,16 @@ namespace Notesnook.API.Controllers return Ok(); } - private static async Task MarkMonographForSyncAsync(string userId, string monographId, string? deviceId, string? jti) + private async Task MarkMonographForSyncAsync(string userId, string monographId, string? deviceId, string? jti) { if (deviceId == null) return; - new SyncDeviceService(new SyncDevice(userId, deviceId)).AddIdsToOtherDevices([$"{monographId}:monograph"]); - await SendTriggerSyncEventAsync(userId, jti); + await syncDeviceService.AddIdsToOtherDevicesAsync(userId, deviceId, [new(monographId, "monograph")]); } - private static async Task MarkMonographForSyncAsync(string userId, string monographId) + private async Task MarkMonographForSyncAsync(string userId, string monographId) { - new SyncDeviceService(new SyncDevice(userId, string.Empty)).AddIdsToAllDevices([$"{monographId}:monograph"]); - await SendTriggerSyncEventAsync(userId, sendToAllDevices: true); - } - - private static async Task SendTriggerSyncEventAsync(string userId, string? jti = null, bool sendToAllDevices = false) - { - await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage - { - OriginTokenId = sendToAllDevices ? null : jti, - UserId = userId, - Message = new Message - { - Type = "triggerSync", - Data = JsonSerializer.Serialize(new { reason = "Monographs updated." }) - } - }); + await syncDeviceService.AddIdsToAllDevicesAsync(userId, [new(monographId, "monograph")]); } private async Task CleanupContentAsync(ClaimsPrincipal user, string? content) diff --git a/Notesnook.API/Controllers/S3Controller.cs b/Notesnook.API/Controllers/S3Controller.cs index b912ded..8292a04 100644 --- a/Notesnook.API/Controllers/S3Controller.cs +++ b/Notesnook.API/Controllers/S3Controller.cs @@ -17,23 +17,25 @@ You should have received a copy of the Affero GNU General Public License along with this program. If not, see . */ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Amazon.S3.Model; -using System.Threading.Tasks; -using System.Security.Claims; -using Notesnook.API.Interfaces; using System; using System.Net.Http; -using Streetwriters.Common.Extensions; -using Streetwriters.Common.Models; -using Notesnook.API.Helpers; -using Streetwriters.Common; -using Streetwriters.Common.Interfaces; -using Notesnook.API.Models; +using System.Security.Claims; +using System.Threading.Tasks; +using Amazon.S3.Model; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using MongoDB.Driver; +using Notesnook.API.Accessors; +using Notesnook.API.Helpers; +using Notesnook.API.Interfaces; +using Notesnook.API.Models; +using Streetwriters.Common; +using Streetwriters.Common.Accessors; +using Streetwriters.Common.Extensions; +using Streetwriters.Common.Interfaces; +using Streetwriters.Common.Models; namespace Notesnook.API.Controllers { @@ -41,7 +43,7 @@ namespace Notesnook.API.Controllers [Route("s3")] [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] [Authorize("Sync")] - public class S3Controller(IS3Service s3Service, ISyncItemsRepositoryAccessor repositories, ILogger logger) : ControllerBase + public class S3Controller(IS3Service s3Service, ISyncItemsRepositoryAccessor repositories, WampServiceAccessor serviceAccessor, ILogger logger) : ControllerBase { [HttpPut] public async Task Upload([FromQuery] string name) @@ -74,8 +76,7 @@ namespace Notesnook.API.Controllers { var userSettings = await repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId); - var subscriptionService = await WampServers.SubscriptionServer.GetServiceAsync(SubscriptionServerTopics.UserSubscriptionServiceTopic); - var subscription = await subscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User subscription not found."); + var subscription = await serviceAccessor.UserSubscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User subscription not found."); if (StorageHelper.IsFileSizeExceeded(subscription, fileSize)) throw new Exception("Max file size exceeded."); diff --git a/Notesnook.API/Controllers/SyncDeviceController.cs b/Notesnook.API/Controllers/SyncDeviceController.cs index 5c8fe98..0830aa1 100644 --- a/Notesnook.API/Controllers/SyncDeviceController.cs +++ b/Notesnook.API/Controllers/SyncDeviceController.cs @@ -37,15 +37,15 @@ namespace Notesnook.API.Controllers [ApiController] [Authorize] [Route("devices")] - public class SyncDeviceController(ILogger logger) : ControllerBase + public class SyncDeviceController(SyncDeviceService syncDeviceService, ILogger logger) : ControllerBase { [HttpPost] - public IActionResult RegisterDevice([FromQuery] string deviceId) + public async Task RegisterDevice([FromQuery] string deviceId) { try { var userId = this.User.GetUserId(); - new SyncDeviceService(new SyncDevice(userId, deviceId)).RegisterDevice(); + await syncDeviceService.RegisterDeviceAsync(userId, deviceId); return Ok(); } catch (Exception ex) @@ -57,12 +57,12 @@ namespace Notesnook.API.Controllers [HttpDelete] - public IActionResult UnregisterDevice([FromQuery] string deviceId) + public async Task UnregisterDevice([FromQuery] string deviceId) { try { var userId = this.User.GetUserId(); - new SyncDeviceService(new SyncDevice(userId, deviceId)).UnregisterDevice(); + await syncDeviceService.UnregisterDeviceAsync(userId, deviceId); return Ok(); } catch (Exception ex) diff --git a/Notesnook.API/Hubs/SyncV2Hub.cs b/Notesnook.API/Hubs/SyncV2Hub.cs index 192e1d3..e9aedc8 100644 --- a/Notesnook.API/Hubs/SyncV2Hub.cs +++ b/Notesnook.API/Hubs/SyncV2Hub.cs @@ -46,12 +46,14 @@ namespace Notesnook.API.Hubs Task SendMonographs(IEnumerable monographs); Task SendInboxItems(IEnumerable inboxItems); Task PushCompleted(); + Task PushCompletedV2(string deviceId); } [Authorize] public class SyncV2Hub : Hub { private ISyncItemsRepositoryAccessor Repositories { get; } + private SyncDeviceService SyncDeviceService { get; } private readonly IUnitOfWork unit; private static readonly string[] CollectionKeys = [ "settingitem", @@ -67,14 +69,15 @@ namespace Notesnook.API.Hubs "relation", // relations must sync at the end to prevent invalid state ]; private readonly FrozenDictionary, string, long>> UpsertActionsMap; - private readonly Func>>[] Collections; + private readonly Func, bool, int, Task>>[] Collections; ILogger Logger { get; } - public SyncV2Hub(ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor, IUnitOfWork unitOfWork, ILogger logger) + public SyncV2Hub(ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor, IUnitOfWork unitOfWork, SyncDeviceService syncDeviceService, ILogger logger) { Logger = logger; Repositories = syncItemsRepositoryAccessor; unit = unitOfWork; + SyncDeviceService = syncDeviceService; Collections = [ Repositories.Settings.FindItemsById, @@ -133,7 +136,7 @@ namespace Notesnook.API.Hubs if (!await unit.Commit()) return 0; - new SyncDeviceService(new SyncDevice(userId, deviceId)).AddIdsToOtherDevices(pushItem.Items.Select((i) => $"{i.ItemId}:{pushItem.Type}").ToList()); + await SyncDeviceService.AddIdsToOtherDevicesAsync(userId, deviceId, pushItem.Items.Select((i) => new ItemKey(i.ItemId, pushItem.Type))); return 1; } finally @@ -149,14 +152,22 @@ namespace Notesnook.API.Hubs return true; } - private async IAsyncEnumerable PrepareChunks(string userId, string[] ids, int size, bool resetSync, long maxBytes) + public async Task PushCompletedV2(string deviceId) + { + var userId = Context.User?.FindFirstValue("sub") ?? throw new HubException("User not found."); + await Clients.OthersInGroup(userId).PushCompleted(); + await Clients.OthersInGroup(userId).PushCompletedV2(deviceId); + return true; + } + + private async IAsyncEnumerable PrepareChunks(string userId, HashSet ids, int size, bool resetSync, long maxBytes) { var itemsProcessed = 0; for (int i = 0; i < Collections.Length; i++) { var type = CollectionKeys[i]; - var filteredIds = ids.Where((id) => id.EndsWith($":{type}")).Select((id) => id.Split(":")[0]).ToArray(); + var filteredIds = ids.Where((id) => id.Type == type).Select((id) => id.ItemId).ToArray(); if (!resetSync && filteredIds.Length == 0) continue; using var cursor = await Collections[i](userId, filteredIds, resetSync, size); @@ -220,61 +231,47 @@ namespace Notesnook.API.Hubs SyncEventCounterSource.Log.FetchV2(); - var device = new SyncDevice(userId, deviceId); - var deviceService = new SyncDeviceService(device); - if (!deviceService.IsDeviceRegistered()) deviceService.RegisterDevice(); - - device.LastAccessTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - - var isResetSync = deviceService.IsSyncReset(); - if (!deviceService.IsUnsynced() && - !deviceService.IsSyncPending() && - !isResetSync) - return new SyncV2Metadata { Synced = true }; + var device = await SyncDeviceService.GetDeviceAsync(userId, deviceId); + if (device == null) + device = await SyncDeviceService.RegisterDeviceAsync(userId, deviceId); + else + await SyncDeviceService.UpdateLastAccessTimeAsync(userId, deviceId); var stopwatch = Stopwatch.StartNew(); try { - string[] ids = deviceService.FetchUnsyncedIds(); + var ids = await SyncDeviceService.FetchUnsyncedIdsAsync(userId, deviceId); + if (!device.IsSyncReset && ids.Count == 0) + return new SyncV2Metadata { Synced = true }; var chunks = PrepareChunks( userId, ids, size: 1000, - resetSync: isResetSync, + resetSync: device.IsSyncReset, maxBytes: 7 * 1024 * 1024 ); - var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId.Equals(userId)); - if (userSettings.VaultKey != null) - { - if (!await Clients.Caller.SendVaultKey(userSettings.VaultKey).WaitAsync(TimeSpan.FromMinutes(10))) throw new HubException("Client rejected vault key."); - } - - await foreach (var chunk in chunks) { if (!await Clients.Caller.SendItems(chunk).WaitAsync(TimeSpan.FromMinutes(10))) throw new HubException("Client rejected sent items."); - if (!isResetSync) + if (!device.IsSyncReset) { - var syncedIds = chunk.Items.Select((i) => $"{i.ItemId}:{chunk.Type}").ToHashSet(); - ids = ids.Where((id) => !syncedIds.Contains(id)).ToArray(); - deviceService.WritePendingIds(ids); + ids.ExceptWith(chunk.Items.Select(i => new ItemKey(i.ItemId, chunk.Type))); + await SyncDeviceService.WritePendingIdsAsync(userId, deviceId, ids); } } if (includeMonographs) { - var isSyncingMonographsForFirstTime = !device.HasInitialMonographsSync; - var unsyncedMonographs = ids.Where((id) => id.EndsWith(":monograph")).ToHashSet(); - var unsyncedMonographIds = unsyncedMonographs.Select((id) => id.Split(":")[0]).ToArray(); - FilterDefinition filter = isResetSync || isSyncingMonographsForFirstTime - ? Builders.Filter.Eq("UserId", userId) + var unsyncedMonographIds = ids.Where(k => k.Type == "monograph").Select(k => k.ItemId); + FilterDefinition filter = device.IsSyncReset + ? Builders.Filter.Eq(m => m.UserId, userId) : Builders.Filter.And( - Builders.Filter.Eq("UserId", userId), + Builders.Filter.Eq(m => m.UserId, userId), Builders.Filter.Or( - Builders.Filter.In("ItemId", unsyncedMonographIds), + Builders.Filter.In(m => m.ItemId, unsyncedMonographIds), Builders.Filter.In("_id", unsyncedMonographIds) ) ); @@ -285,30 +282,26 @@ namespace Notesnook.API.Hubs Password = m.Password, SelfDestruct = m.SelfDestruct, Title = m.Title, - ItemId = m.ItemId ?? m.Id.ToString(), - ViewCount = m.ViewCount + ItemId = m.ItemId ?? m.Id.ToString() }).ToListAsync(); if (userMonographs.Count > 0 && !await Clients.Caller.SendMonographs(userMonographs).WaitAsync(TimeSpan.FromMinutes(10))) throw new HubException("Client rejected monographs."); - - device.HasInitialMonographsSync = true; } if (includeInboxItems) { - var unsyncedInboxItems = ids.Where((id) => id.EndsWith(":inboxItems")).ToHashSet(); - var unsyncedInboxItemIds = unsyncedInboxItems.Select((id) => id.Split(":")[0]).ToArray(); - var userInboxItems = isResetSync + var unsyncedInboxItemIds = ids.Where(k => k.Type == "inbox_item").Select(k => k.ItemId); + var userInboxItems = device.IsSyncReset ? await Repositories.InboxItems.FindAsync(m => m.UserId == userId) - : await Repositories.InboxItems.FindAsync(m => m.UserId == userId && unsyncedInboxItemIds.Contains(m.ItemId)); + : await Repositories.InboxItems.FindAsync(m => m.UserId == userId && unsyncedInboxItemIds.Contains(m.ItemId ?? m.Id.ToString())); if (userInboxItems.Any() && !await Clients.Caller.SendInboxItems(userInboxItems).WaitAsync(TimeSpan.FromMinutes(10))) { throw new HubException("Client rejected inbox items."); } } - deviceService.Reset(); + await SyncDeviceService.ResetAsync(userId, deviceId); return new SyncV2Metadata { diff --git a/Notesnook.API/Interfaces/ISyncItemsRepositoryAccessor.cs b/Notesnook.API/Interfaces/ISyncItemsRepositoryAccessor.cs index 295c353..80611df 100644 --- a/Notesnook.API/Interfaces/ISyncItemsRepositoryAccessor.cs +++ b/Notesnook.API/Interfaces/ISyncItemsRepositoryAccessor.cs @@ -42,5 +42,7 @@ namespace Notesnook.API.Interfaces Repository Monographs { get; } Repository InboxApiKey { get; } Repository InboxItems { get; } + Repository SyncDevices { get; } + Repository DeviceIdsChunks { get; } } } \ No newline at end of file diff --git a/Notesnook.API/Jobs/DeviceCleanupJob.cs b/Notesnook.API/Jobs/DeviceCleanupJob.cs index eb849f7..103ff38 100644 --- a/Notesnook.API/Jobs/DeviceCleanupJob.cs +++ b/Notesnook.API/Jobs/DeviceCleanupJob.cs @@ -1,65 +1,41 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; +using MongoDB.Driver; +using Notesnook.API.Interfaces; +using Notesnook.API.Models; +using Notesnook.API.Services; using Quartz; namespace Notesnook.API.Jobs { - public class DeviceCleanupJob : IJob + public class DeviceCleanupJob(ISyncItemsRepositoryAccessor repositories) : IJob { - public Task Execute(IJobExecutionContext context) + public async Task Execute(IJobExecutionContext context) { - ParallelOptions parallelOptions = new() + var cutoffDate = DateTimeOffset.UtcNow.AddMonths(-1).ToUnixTimeMilliseconds(); + var deviceFilter = Builders.Filter.Lt(x => x.LastAccessTime, cutoffDate); + + using var cursor = await repositories.SyncDevices.Collection.Find(deviceFilter, new FindOptions { BatchSize = 1000 }) + .Project(x => x.DeviceId) + .ToCursorAsync(); + + var deleteModels = new List>(); + while (await cursor.MoveNextAsync()) { - MaxDegreeOfParallelism = 100, - CancellationToken = context.CancellationToken, - }; - Parallel.ForEach(Directory.EnumerateDirectories("sync"), parallelOptions, (userDir, ct) => + if (!cursor.Current.Any()) continue; + deleteModels.Add(new DeleteManyModel(Builders.Filter.In(x => x.DeviceId, cursor.Current))); + } + + if (deleteModels.Count > 0) { - foreach (var device in Directory.EnumerateDirectories(userDir)) - { - string lastAccessFile = Path.Combine(device, "LastAccessTime"); + var bulkOptions = new BulkWriteOptions { IsOrdered = false }; + await repositories.DeviceIdsChunks.Collection.BulkWriteAsync(deleteModels, bulkOptions); + } - try - { - if (!File.Exists(lastAccessFile)) - { - Directory.Delete(device, true); - continue; - } - - string content = File.ReadAllText(lastAccessFile); - if (!long.TryParse(content, out long lastAccessTime) || lastAccessTime <= 0) - { - Directory.Delete(device, true); - continue; - } - - DateTimeOffset accessTime; - try - { - accessTime = DateTimeOffset.FromUnixTimeMilliseconds(lastAccessTime); - } - catch (Exception) - { - Directory.Delete(device, true); - continue; - } - - // If the device hasn't been accessed for more than one month, delete it. - if (accessTime.AddMonths(1) < DateTimeOffset.UtcNow) - { - Directory.Delete(device, true); - } - } - catch (Exception ex) - { - // Log the error and continue processing other directories. - Console.Error.WriteLine($"Error processing device '{device}': {ex.Message}"); - } - } - }); - return Task.CompletedTask; + await repositories.SyncDevices.Collection.DeleteManyAsync(deviceFilter); } } } \ No newline at end of file diff --git a/Notesnook.API/Models/DeviceIdsChunk.cs b/Notesnook.API/Models/DeviceIdsChunk.cs new file mode 100644 index 0000000..d679ca1 --- /dev/null +++ b/Notesnook.API/Models/DeviceIdsChunk.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Notesnook.API.Models +{ + public class DeviceIdsChunk + { + [BsonId] + public ObjectId Id { get; set; } + public required string UserId { get; set; } + public required string DeviceId { get; set; } + public required string Key { get; set; } + public required string[] Ids { get; set; } = []; + } +} \ No newline at end of file diff --git a/Notesnook.API/Models/MonographMetadata.cs b/Notesnook.API/Models/MonographMetadata.cs index f45477f..73391af 100644 --- a/Notesnook.API/Models/MonographMetadata.cs +++ b/Notesnook.API/Models/MonographMetadata.cs @@ -17,10 +17,10 @@ You should have received a copy of the Affero GNU General Public License along with this program. If not, see . */ +using System.Runtime.Serialization; using System.Text.Json.Serialization; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using System.Runtime.Serialization; namespace Notesnook.API.Models { @@ -48,8 +48,5 @@ namespace Notesnook.API.Models [JsonPropertyName("deleted")] public bool Deleted { get; set; } - - [JsonPropertyName("viewCount")] - public int ViewCount { get; set; } } } \ No newline at end of file diff --git a/Notesnook.API/Models/SyncDevice.cs b/Notesnook.API/Models/SyncDevice.cs new file mode 100644 index 0000000..e9abe43 --- /dev/null +++ b/Notesnook.API/Models/SyncDevice.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Notesnook.API.Models +{ + public class SyncDevice + { + [BsonId] + public ObjectId Id { get; set; } + + public required string UserId { get; set; } + public required string DeviceId { get; set; } + public required long LastAccessTime { get; set; } + public required bool IsSyncReset { get; set; } + public string? AppVersion { get; set; } + public string? DatabaseVersion { get; set; } + } +} \ No newline at end of file diff --git a/Notesnook.API/Program.cs b/Notesnook.API/Program.cs index cc19581..f67e5c8 100644 --- a/Notesnook.API/Program.cs +++ b/Notesnook.API/Program.cs @@ -17,13 +17,12 @@ You should have received a copy of the Affero GNU General Public License along with this program. If not, see . */ -using System; +using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Streetwriters.Common; -using System.Net; namespace Notesnook.API { diff --git a/Notesnook.API/Services/S3Service.cs b/Notesnook.API/Services/S3Service.cs index 6d7ac93..240c951 100644 --- a/Notesnook.API/Services/S3Service.cs +++ b/Notesnook.API/Services/S3Service.cs @@ -30,10 +30,12 @@ using Amazon.S3.Model; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Driver; +using Notesnook.API.Accessors; using Notesnook.API.Helpers; using Notesnook.API.Interfaces; using Notesnook.API.Models; using Streetwriters.Common; +using Streetwriters.Common.Accessors; using Streetwriters.Common.Enums; using Streetwriters.Common.Interfaces; using Streetwriters.Common.Models; @@ -52,6 +54,7 @@ namespace Notesnook.API.Services private readonly string INTERNAL_BUCKET_NAME = Constants.S3_INTERNAL_BUCKET_NAME ?? Constants.S3_BUCKET_NAME; private readonly S3FailoverHelper S3Client; private ISyncItemsRepositoryAccessor Repositories { get; } + private WampServiceAccessor ServiceAccessor { get; } // When running in a dockerized environment the sync server doesn't have access // to the host's S3 Service URL. It can only talk to S3 server via its own internal @@ -64,9 +67,10 @@ namespace Notesnook.API.Services private readonly S3FailoverHelper S3InternalClient; private readonly HttpClient httpClient = new(); - public S3Service(ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor, ILogger logger) + public S3Service(ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor, WampServiceAccessor wampServiceAccessor, ILogger logger) { Repositories = syncItemsRepositoryAccessor; + ServiceAccessor = wampServiceAccessor; S3Client = new S3FailoverHelper( S3ClientFactory.CreateS3Clients( Constants.S3_SERVICE_URL, @@ -240,8 +244,7 @@ namespace Notesnook.API.Services if (!Constants.IS_SELF_HOSTED) { - var subscriptionService = await WampServers.SubscriptionServer.GetServiceAsync(SubscriptionServerTopics.UserSubscriptionServiceTopic); - var subscription = await subscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User subscription not found."); + var subscription = await ServiceAccessor.UserSubscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User subscription not found."); long fileSize = await GetMultipartUploadSizeAsync(userId, uploadRequest.Key, uploadRequest.UploadId); if (StorageHelper.IsFileSizeExceeded(subscription, fileSize)) diff --git a/Notesnook.API/Services/SyncDeviceService.cs b/Notesnook.API/Services/SyncDeviceService.cs index 1fd036d..bd99082 100644 --- a/Notesnook.API/Services/SyncDeviceService.cs +++ b/Notesnook.API/Services/SyncDeviceService.cs @@ -24,220 +24,201 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using Notesnook.API.Interfaces; +using Notesnook.API.Models; namespace Notesnook.API.Services { - public struct SyncDevice(string userId, string deviceId) + public readonly record struct ItemKey(string ItemId, string Type) { - public readonly string DeviceId => deviceId; - public readonly string UserId => userId; - - public string UserSyncDirectoryPath = CreateFilePath(userId); - public string UserDeviceDirectoryPath = CreateFilePath(userId, deviceId); - public string PendingIdsFilePath = CreateFilePath(userId, deviceId, "pending"); - public string UnsyncedIdsFilePath = CreateFilePath(userId, deviceId, "unsynced"); - public string ResetSyncFilePath = CreateFilePath(userId, deviceId, "reset-sync"); - - public readonly long LastAccessTime - { - get => long.Parse(GetMetadata("LastAccessTime") ?? "0"); - set => SetMetadata("LastAccessTime", value.ToString()); - } - - /// - /// Indicates if the monographs have been synced for the first time - /// ever on a device. - /// - public readonly bool HasInitialMonographsSync - { - get => !string.IsNullOrEmpty(GetMetadata("HasInitialMonographsSync")); - set => SetMetadata("HasInitialMonographsSync", value.ToString()); - } - - private static string CreateFilePath(string userId, string? deviceId = null, string? metadataKey = null) - { - return Path.Join("sync", userId, deviceId, metadataKey); - } - - private readonly string? GetMetadata(string metadataKey) - { - var path = CreateFilePath(userId, deviceId, metadataKey); - if (!File.Exists(path)) return null; - return File.ReadAllText(path); - } - - private readonly void SetMetadata(string metadataKey, string value) - { - try - { - var path = CreateFilePath(userId, deviceId, metadataKey); - File.WriteAllText(path, value); - } - catch (DirectoryNotFoundException) { } - } + public override string ToString() => $"{ItemId}:{Type}"; } - - public class SyncDeviceService(SyncDevice device) + public class SyncDeviceService(ISyncItemsRepositoryAccessor repositories, ILogger logger) { - public string[] GetUnsyncedIds() - { - try - { - return File.ReadAllLines(device.UnsyncedIdsFilePath); - } - catch { return []; } - } + private static FilterDefinition DeviceFilter(string userId, string deviceId) => + Builders.Filter.Eq(x => x.UserId, userId) & + Builders.Filter.Eq(x => x.DeviceId, deviceId); + private static FilterDefinition DeviceIdsChunkFilter(string userId, string deviceId, string key) => + Builders.Filter.Eq(x => x.UserId, userId) & + Builders.Filter.Eq(x => x.DeviceId, deviceId) & + Builders.Filter.Eq(x => x.Key, key); - public string[] GetUnsyncedIds(string deviceId) - { - try - { - return File.ReadAllLines(Path.Join(device.UserSyncDirectoryPath, deviceId, "unsynced")); - } - catch { return []; } - } + private static FilterDefinition DeviceIdsChunkFilter(string userId, string deviceId) => + Builders.Filter.Eq(x => x.UserId, userId) & + Builders.Filter.Eq(x => x.DeviceId, deviceId); - public string[] FetchUnsyncedIds() + private static FilterDefinition DeviceIdsChunkFilter(string userId) => + Builders.Filter.Eq(x => x.UserId, userId); + + private static FilterDefinition UserFilter(string userId) => Builders.Filter.Eq(x => x.UserId, userId); + + + public async Task> GetIdsAsync(string userId, string deviceId, string key) { - if (IsSyncReset()) return []; - try + var cursor = await repositories.DeviceIdsChunks.Collection.FindAsync(DeviceIdsChunkFilter(userId, deviceId, key)); + var result = new HashSet(); + while (await cursor.MoveNextAsync()) { - var unsyncedIds = GetUnsyncedIds(); - lock (device.DeviceId) + foreach (var chunk in cursor.Current) { - if (IsSyncPending()) + foreach (var id in chunk.Ids) { - unsyncedIds = unsyncedIds.Union(File.ReadAllLines(device.PendingIdsFilePath)).ToArray(); + var parts = id.Split(':', 2); + result.Add(new ItemKey(parts[0], parts[1])); } - - if (unsyncedIds.Length == 0) return []; - - File.Delete(device.UnsyncedIdsFilePath); - File.WriteAllLines(device.PendingIdsFilePath, unsyncedIds); } - return unsyncedIds; } - catch + return result; + } + + const int MaxIdsPerChunk = 400_000; + public async Task AppendIdsAsync(string userId, string deviceId, string key, IEnumerable ids) + { + var filter = DeviceIdsChunkFilter(userId, deviceId, key) & Builders.Filter.Where(x => x.Ids.Length < MaxIdsPerChunk); + var chunk = await repositories.DeviceIdsChunks.Collection.Find(filter).FirstOrDefaultAsync(); + + if (chunk != null) { - return []; + var update = Builders.Update.PushEach(x => x.Ids, ids.Select(i => i.ToString())); + await repositories.DeviceIdsChunks.Collection.UpdateOneAsync( + Builders.Filter.Eq(x => x.Id, chunk.Id), + update + ); } - } - - public void WritePendingIds(IEnumerable ids) - { - lock (device.DeviceId) + else { - File.WriteAllLines(device.PendingIdsFilePath, ids); - } - } - - public bool IsSyncReset() - { - return File.Exists(device.ResetSyncFilePath); - } - public bool IsSyncReset(string deviceId) - { - return File.Exists(Path.Join(device.UserSyncDirectoryPath, deviceId, "reset-sync")); - } - - public bool IsSyncPending() - { - return File.Exists(device.PendingIdsFilePath); - } - - public bool IsUnsynced() - { - return File.Exists(device.UnsyncedIdsFilePath); - } - - public void Reset() - { - try - { - lock (device.UserId) + var newChunk = new DeviceIdsChunk { - File.Delete(device.ResetSyncFilePath); - File.Delete(device.PendingIdsFilePath); - } + UserId = userId, + DeviceId = deviceId, + Key = key, + Ids = [.. ids.Select(i => i.ToString())] + }; + await repositories.DeviceIdsChunks.Collection.InsertOneAsync(newChunk); } - catch (FileNotFoundException) { } - catch (DirectoryNotFoundException) { } + + var emptyChunksFilter = DeviceIdsChunkFilter(userId, deviceId, key) & Builders.Filter.Size(x => x.Ids, 0); + await repositories.DeviceIdsChunks.Collection.DeleteManyAsync(emptyChunksFilter); } - public bool IsDeviceRegistered() + public async Task WriteIdsAsync(string userId, string deviceId, string key, IEnumerable ids) { - return Directory.Exists(device.UserDeviceDirectoryPath); - } - public bool IsDeviceRegistered(string deviceId) - { - return Directory.Exists(Path.Join(device.UserSyncDirectoryPath, deviceId)); - } - - public string[] ListDevices() - { - return Directory.GetDirectories(device.UserSyncDirectoryPath).Select((path) => path[(path.LastIndexOf(Path.DirectorySeparatorChar) + 1)..]).ToArray(); - } - - public void ResetDevices() - { - lock (device.UserId) + var writes = new List> { - if (File.Exists(device.UserSyncDirectoryPath)) File.Delete(device.UserSyncDirectoryPath); - Directory.CreateDirectory(device.UserSyncDirectoryPath); - } - } - - public void AddIdsToOtherDevices(List ids) - { - device.LastAccessTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - foreach (string id in ListDevices()) + new DeleteManyModel(DeviceIdsChunkFilter(userId, deviceId, key)) + }; + var chunks = ids.Chunk(MaxIdsPerChunk); + foreach (var chunk in chunks) { - if (id == device.DeviceId || IsSyncReset(id)) continue; - - lock (id) + var newChunk = new DeviceIdsChunk { - if (!IsDeviceRegistered(id)) Directory.CreateDirectory(Path.Join(device.UserSyncDirectoryPath, id)); - - var oldIds = GetUnsyncedIds(id); - File.WriteAllLines(Path.Join(device.UserSyncDirectoryPath, id, "unsynced"), ids.Union(oldIds)); - } + UserId = userId, + DeviceId = deviceId, + Key = key, + Ids = [.. chunk.Select(i => i.ToString())] + }; + writes.Add(new InsertOneModel(newChunk)); } + await repositories.DeviceIdsChunks.Collection.BulkWriteAsync(writes); } - public void AddIdsToAllDevices(List ids) + public async Task> FetchUnsyncedIdsAsync(string userId, string deviceId) { - foreach (var id in ListDevices()) + var device = await GetDeviceAsync(userId, deviceId); + if (device == null || device.IsSyncReset) return []; + + var unsyncedIds = await GetIdsAsync(userId, deviceId, "unsynced"); + var pendingIds = await GetIdsAsync(userId, deviceId, "pending"); + + unsyncedIds = [.. unsyncedIds, .. pendingIds]; + + if (unsyncedIds.Count == 0) return []; + + await repositories.DeviceIdsChunks.Collection.DeleteManyAsync(DeviceIdsChunkFilter(userId, deviceId, "unsynced")); + await WriteIdsAsync(userId, deviceId, "pending", unsyncedIds); + + return unsyncedIds; + } + + public async Task WritePendingIdsAsync(string userId, string deviceId, HashSet ids) + { + await WriteIdsAsync(userId, deviceId, "pending", ids); + } + + public async Task ResetAsync(string userId, string deviceId) + { + await repositories.SyncDevices.Collection.UpdateOneAsync(DeviceFilter(userId, deviceId), Builders.Update + .Set(x => x.IsSyncReset, false)); + await repositories.DeviceIdsChunks.Collection.DeleteManyAsync(DeviceIdsChunkFilter(userId, deviceId, "pending")); + } + + public async Task GetDeviceAsync(string userId, string deviceId) + { + return await repositories.SyncDevices.Collection.Find(DeviceFilter(userId, deviceId)).FirstOrDefaultAsync(); + } + + public async IAsyncEnumerable ListDevicesAsync(string userId) + { + using var cursor = await repositories.SyncDevices.Collection.FindAsync(UserFilter(userId)); + while (await cursor.MoveNextAsync()) { - if (IsSyncReset(id)) return; - lock (id) + foreach (var device in cursor.Current) { - if (!IsDeviceRegistered(id)) Directory.CreateDirectory(Path.Join(device.UserSyncDirectoryPath, id)); - - var oldIds = GetUnsyncedIds(id); - File.WriteAllLinesAsync(Path.Join(device.UserSyncDirectoryPath, id, "unsynced"), ids.Union(oldIds)); + yield return device; } } } - public void RegisterDevice() + public async Task ResetDevicesAsync(string userId) { - lock (device.UserId) + await repositories.SyncDevices.Collection.DeleteManyAsync(UserFilter(userId)); + await repositories.DeviceIdsChunks.Collection.DeleteManyAsync(DeviceIdsChunkFilter(userId)); + } + + public async Task UpdateLastAccessTimeAsync(string userId, string deviceId) + { + await repositories.SyncDevices.Collection.UpdateOneAsync(DeviceFilter(userId, deviceId), Builders.Update + .Set(x => x.LastAccessTime, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())); + } + + public async Task AddIdsToOtherDevicesAsync(string userId, string deviceId, IEnumerable ids) + { + await UpdateLastAccessTimeAsync(userId, deviceId); + await foreach (var device in ListDevicesAsync(userId)) { - if (Directory.Exists(device.UserDeviceDirectoryPath)) - Directory.Delete(device.UserDeviceDirectoryPath, true); - Directory.CreateDirectory(device.UserDeviceDirectoryPath); - File.Create(device.ResetSyncFilePath).Close(); - device.LastAccessTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (device.DeviceId == deviceId || device.IsSyncReset) continue; + await AppendIdsAsync(userId, device.DeviceId, "unsynced", ids); } } - public void UnregisterDevice() + public async Task AddIdsToAllDevicesAsync(string userId, IEnumerable ids) { - lock (device.UserId) + await foreach (var device in ListDevicesAsync(userId)) { - if (!Path.Exists(device.UserDeviceDirectoryPath)) return; - Directory.Delete(device.UserDeviceDirectoryPath, true); + if (device.IsSyncReset) continue; + await AppendIdsAsync(userId, device.DeviceId, "unsynced", ids); } } + + public async Task RegisterDeviceAsync(string userId, string deviceId) + { + var newDevice = new SyncDevice + { + UserId = userId, + DeviceId = deviceId, + LastAccessTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + IsSyncReset = true + }; + await repositories.SyncDevices.Collection.InsertOneAsync(newDevice); + return newDevice; + } + + public async Task UnregisterDeviceAsync(string userId, string deviceId) + { + await repositories.SyncDevices.Collection.DeleteOneAsync(DeviceFilter(userId, deviceId)); + await repositories.DeviceIdsChunks.Collection.DeleteManyAsync(DeviceIdsChunkFilter(userId, deviceId)); + } } } \ No newline at end of file diff --git a/Notesnook.API/Services/UserService.cs b/Notesnook.API/Services/UserService.cs index 5d55bc7..f1e3b87 100644 --- a/Notesnook.API/Services/UserService.cs +++ b/Notesnook.API/Services/UserService.cs @@ -30,9 +30,9 @@ using Notesnook.API.Interfaces; using Notesnook.API.Models; using Notesnook.API.Models.Responses; using Streetwriters.Common; +using Streetwriters.Common.Accessors; using Streetwriters.Common.Enums; using Streetwriters.Common.Extensions; -using Streetwriters.Common.Interfaces; using Streetwriters.Common.Messages; using Streetwriters.Common.Models; using Streetwriters.Data.Interfaces; @@ -41,7 +41,7 @@ namespace Notesnook.API.Services { public class UserService(IHttpContextAccessor accessor, ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor, - IUnitOfWork unitOfWork, IS3Service s3Service, ILogger logger) : IUserService + IUnitOfWork unitOfWork, IS3Service s3Service, SyncDeviceService syncDeviceService, WampServiceAccessor serviceAccessor, ILogger logger) : IUserService { private static readonly System.Security.Cryptography.RandomNumberGenerator Rng = System.Security.Cryptography.RandomNumberGenerator.Create(); private readonly HttpClient httpClient = new(); @@ -88,9 +88,7 @@ namespace Notesnook.API.Services public async Task GetUserAsync(string userId) { - var userService = await WampServers.IdentityServer.GetServiceAsync(IdentityServerTopics.UserAccountServiceTopic); - - var user = await userService.GetUserAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User not found."); + var user = await serviceAccessor.UserAccountService.GetUserAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User not found."); Subscription? subscription = null; if (Constants.IS_SELF_HOSTED) @@ -109,8 +107,7 @@ namespace Notesnook.API.Services } else { - var subscriptionService = await WampServers.SubscriptionServer.GetServiceAsync(SubscriptionServerTopics.UserSubscriptionServiceTopic); - subscription = await subscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User subscription not found."); + subscription = await serviceAccessor.UserSubscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User subscription not found."); } var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == user.UserId) ?? throw new Exception("User settings not found."); @@ -185,8 +182,6 @@ namespace Notesnook.API.Services public async Task DeleteUserAsync(string userId) { - new SyncDeviceService(new SyncDevice(userId, userId)).ResetDevices(); - var cc = new CancellationTokenSource(); Repositories.Notes.DeleteByUserId(userId); @@ -209,6 +204,8 @@ namespace Notesnook.API.Services logger.LogInformation("User data deleted for user {UserId}: {Result}", userId, result); if (!result) throw new Exception("Could not delete user data."); + await syncDeviceService.ResetDevicesAsync(userId); + if (!Constants.IS_SELF_HOSTED) { await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage @@ -225,8 +222,7 @@ namespace Notesnook.API.Services { logger.LogInformation("Deleting user account: {UserId}", userId); - var userService = await WampServers.IdentityServer.GetServiceAsync(IdentityServerTopics.UserAccountServiceTopic); - await userService.DeleteUserAsync(Clients.Notesnook.Id, userId, password); + await serviceAccessor.UserAccountService.DeleteUserAsync(Clients.Notesnook.Id, userId, password); await DeleteUserAsync(userId); @@ -246,7 +242,6 @@ namespace Notesnook.API.Services public async Task ResetUserAsync(string userId, bool removeAttachments) { - new SyncDeviceService(new SyncDevice(userId, userId)).ResetDevices(); var cc = new CancellationTokenSource(); @@ -266,6 +261,8 @@ namespace Notesnook.API.Services Repositories.InboxApiKey.DeleteMany((t) => t.UserId == userId); if (!await unit.Commit()) return false; + await syncDeviceService.ResetDevicesAsync(userId); + var userSettings = await Repositories.UsersSettings.FindOneAsync((s) => s.UserId == userId); userSettings.AttachmentsKey = null; diff --git a/Notesnook.API/Startup.cs b/Notesnook.API/Startup.cs index 43da9cd..0569d21 100644 --- a/Notesnook.API/Startup.cs +++ b/Notesnook.API/Startup.cs @@ -169,14 +169,19 @@ namespace Notesnook.API if (!BsonClassMap.IsClassMapRegistered(typeof(CallToAction))) BsonClassMap.RegisterClassMap(); + if (!BsonClassMap.IsClassMapRegistered(typeof(SyncDevice))) + BsonClassMap.RegisterClassMap(); + services.AddScoped(); services.AddScoped(); services.AddRepository("user_settings", "notesnook") .AddRepository("monographs", "notesnook") .AddRepository("announcements", "notesnook") + .AddRepository(Collections.DeviceIdsChunksKey, "notesnook") + .AddRepository(Collections.SyncDevicesKey, "notesnook") .AddRepository(Collections.InboxApiKeysKey, "notesnook") - .AddRepository(Collections.InboxItems, "notesnook"); + .AddRepository(Collections.InboxItemsKey, "notesnook"); services.AddMongoCollection(Collections.SettingsKey) .AddMongoCollection(Collections.AttachmentsKey) @@ -190,14 +195,17 @@ namespace Notesnook.API .AddMongoCollection(Collections.TagsKey) .AddMongoCollection(Collections.ColorsKey) .AddMongoCollection(Collections.VaultsKey) - .AddMongoCollection(Collections.InboxItems) + .AddMongoCollection(Collections.InboxItemsKey) .AddMongoCollection(Collections.InboxApiKeysKey); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddWampServiceAccessor(Servers.NotesnookAPI); + services.AddControllers(); services.AddHealthChecks(); diff --git a/Notesnook.API/appsettings.Development.json b/Notesnook.API/appsettings.Development.json index 9cfe28c..812a683 100644 --- a/Notesnook.API/appsettings.Development.json +++ b/Notesnook.API/appsettings.Development.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Debug", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information", "Microsoft.AspNetCore.SignalR": "Trace",