diff --git a/Notesnook.API/Controllers/SyncDeviceController.cs b/Notesnook.API/Controllers/SyncDeviceController.cs
new file mode 100644
index 0000000..7eb0c25
--- /dev/null
+++ b/Notesnook.API/Controllers/SyncDeviceController.cs
@@ -0,0 +1,74 @@
+/*
+This file is part of the Notesnook Sync Server project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the Affero GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Affero GNU General Public License for more details.
+
+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.Http;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Notesnook.API.Interfaces;
+using Notesnook.API.Models.Responses;
+using Notesnook.API.Services;
+using Streetwriters.Common;
+using Streetwriters.Common.Extensions;
+using Streetwriters.Common.Models;
+
+namespace Notesnook.API.Controllers
+{
+ [ApiController]
+ [Authorize]
+ [Route("devices")]
+ public class SyncDeviceController : ControllerBase
+ {
+ [HttpPost]
+ public async Task RegisterDevice([FromQuery] string deviceId)
+ {
+ try
+ {
+ var userId = this.User.FindFirstValue("sub");
+ SyncDeviceService.RegisterDevice(userId, deviceId);
+ return Ok();
+ }
+ catch (Exception ex)
+ {
+ await Slogger.Error(nameof(UnregisterDevice), "Couldn't register device.", ex.ToString());
+ return BadRequest(new { error = ex.Message });
+ }
+ }
+
+
+ [HttpDelete]
+ public async Task UnregisterDevice([FromQuery] string deviceId)
+ {
+ try
+ {
+ var userId = this.User.FindFirstValue("sub");
+ SyncDeviceService.UnregisterDevice(userId, deviceId);
+ return Ok();
+ }
+ catch (Exception ex)
+ {
+ await Slogger.Error(nameof(UnregisterDevice), "Couldn't unregister device.", ex.ToString());
+ return BadRequest(new { error = ex.Message });
+ }
+ }
+ }
+}
diff --git a/Notesnook.API/Hubs/SyncV2Hub.cs b/Notesnook.API/Hubs/SyncV2Hub.cs
new file mode 100644
index 0000000..106ed4e
--- /dev/null
+++ b/Notesnook.API/Hubs/SyncV2Hub.cs
@@ -0,0 +1,284 @@
+/*
+This file is part of the Notesnook Sync Server project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the Affero GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Affero GNU General Public License for more details.
+
+You should have received a copy of the Affero GNU General Public License
+along with this program. If not, see .
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.SignalR;
+using MongoDB.Driver;
+using Notesnook.API.Authorization;
+using Notesnook.API.Interfaces;
+using Notesnook.API.Models;
+using Notesnook.API.Services;
+using Streetwriters.Data.Interfaces;
+
+namespace Notesnook.API.Hubs
+{
+ public interface ISyncV2HubClient
+ {
+ Task SendItems(SyncTransferItemV2 transferItem);
+ Task SendVaultKey(EncryptedData vaultKey);
+ Task PushCompleted();
+ }
+
+ [Authorize("Sync")]
+ public class SyncV2Hub : Hub
+ {
+ private ISyncItemsRepositoryAccessor Repositories { get; }
+ private readonly IUnitOfWork unit;
+ private readonly string[] CollectionKeys = [
+ "settingitem",
+ "attachment",
+ "note",
+ "notebook",
+ "content",
+ "shortcut",
+ "reminder",
+ "color",
+ "tag",
+ "vault",
+ "relation", // relations must sync at the end to prevent invalid state
+ ];
+
+ public SyncV2Hub(ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor, IUnitOfWork unitOfWork)
+ {
+ Repositories = syncItemsRepositoryAccessor;
+ unit = unitOfWork;
+ }
+
+ public override async Task OnConnectedAsync()
+ {
+ var result = new SyncRequirement().IsAuthorized(Context.User, new PathString("/hubs/sync/v2"));
+ if (!result.Succeeded)
+ {
+ var reason = result.AuthorizationFailure.FailureReasons.FirstOrDefault();
+ throw new HubException(reason?.Message ?? "Unauthorized");
+ }
+ var id = Context.User.FindFirstValue("sub");
+ await Groups.AddToGroupAsync(Context.ConnectionId, id);
+ await base.OnConnectedAsync();
+ }
+
+ private Action MapTypeToUpsertAction(string type)
+ {
+ return type switch
+ {
+ "settingitem" => Repositories.Settings.Upsert,
+ "attachment" => Repositories.Attachments.Upsert,
+ "note" => Repositories.Notes.Upsert,
+ "notebook" => Repositories.Notebooks.Upsert,
+ "content" => Repositories.Contents.Upsert,
+ "shortcut" => Repositories.Shortcuts.Upsert,
+ "reminder" => Repositories.Reminders.Upsert,
+ "relation" => Repositories.Relations.Upsert,
+ "color" => Repositories.Colors.Upsert,
+ "vault" => Repositories.Vaults.Upsert,
+ "tag" => Repositories.Tags.Upsert,
+ _ => null,
+ };
+ }
+
+ private Func, bool, int, Task>> MapTypeToFindItemsAction(string type)
+ {
+ return type switch
+ {
+ "settingitem" => Repositories.Settings.FindItemsById,
+ "attachment" => Repositories.Attachments.FindItemsById,
+ "note" => Repositories.Notes.FindItemsById,
+ "notebook" => Repositories.Notebooks.FindItemsById,
+ "content" => Repositories.Contents.FindItemsById,
+ "shortcut" => Repositories.Shortcuts.FindItemsById,
+ "reminder" => Repositories.Reminders.FindItemsById,
+ "relation" => Repositories.Relations.FindItemsById,
+ "color" => Repositories.Colors.FindItemsById,
+ "vault" => Repositories.Vaults.FindItemsById,
+ "tag" => Repositories.Tags.FindItemsById,
+ _ => null,
+ };
+ }
+
+ public async Task PushItems(string deviceId, SyncTransferItemV2 pushItem)
+ {
+ var userId = Context.User.FindFirstValue("sub");
+ if (string.IsNullOrEmpty(userId)) return 0;
+
+ var UpsertItem = MapTypeToUpsertAction(pushItem.Type) ?? throw new Exception($"Invalid item type: {pushItem.Type}.");
+ foreach (var item in pushItem.Items)
+ {
+ UpsertItem(item, userId, 1);
+ }
+
+ if (!await unit.Commit()) return 0;
+
+ await SyncDeviceService.AddIdsToOtherDevicesAsync(userId, deviceId, pushItem.Items.Select((i) => $"{i.ItemId}:{pushItem.Type}").ToList());
+ return 1;
+ }
+
+ public async Task PushCompleted()
+ {
+ var userId = Context.User.FindFirstValue("sub");
+ await Clients.OthersInGroup(userId).PushCompleted();
+ return true;
+ }
+
+ private static async IAsyncEnumerable PrepareChunks(Func>>[] collections, string[] types, string userId, string[] ids, int size, bool resetSync, long maxBytes)
+ {
+ var chunksProcessed = 0;
+ for (int i = 0; i < collections.Length; i++)
+ {
+ var type = types[i];
+
+ var filteredIds = ids.Where((id) => id.EndsWith($":{type}")).Select((id) => id.Split(":")[0]).ToArray();
+ if (!resetSync && filteredIds.Length == 0) continue;
+
+ using var cursor = await collections[i](userId, filteredIds, resetSync, size);
+
+ var chunk = new List();
+ long totalBytes = 0;
+ long METADATA_BYTES = 5 * 1024;
+
+ while (await cursor.MoveNextAsync())
+ {
+ foreach (var item in cursor.Current)
+ {
+ chunk.Add(item);
+ totalBytes += item.Length + METADATA_BYTES;
+ if (totalBytes >= maxBytes)
+ {
+ yield return new SyncTransferItemV2
+ {
+ Items = chunk,
+ Type = type,
+ Count = chunksProcessed
+ };
+
+ totalBytes = 0;
+ chunk.Clear();
+ }
+ }
+ }
+ if (chunk.Count > 0)
+ {
+ yield return new SyncTransferItemV2
+ {
+ Items = chunk,
+ Type = type,
+ Count = chunksProcessed
+ };
+ }
+ }
+ }
+
+ public async Task RequestFetch(string deviceId)
+ {
+ var userId = Context.User.FindFirstValue("sub");
+
+ if (!SyncDeviceService.IsDeviceRegistered(userId, deviceId))
+ SyncDeviceService.RegisterDevice(userId, deviceId);
+
+ var isResetSync = SyncDeviceService.IsSyncReset(userId, deviceId);
+ if (!SyncDeviceService.IsUnsynced(userId, deviceId) &&
+ !SyncDeviceService.IsSyncPending(userId, deviceId) &&
+ !isResetSync)
+ return new SyncV2Metadata { Synced = true };
+
+ string[] ids = await SyncDeviceService.FetchUnsyncedIdsAsync(userId, deviceId);
+
+ var chunks = PrepareChunks(
+ collections: [
+ Repositories.Settings.FindItemsById,
+ Repositories.Attachments.FindItemsById,
+ Repositories.Notes.FindItemsById,
+ Repositories.Notebooks.FindItemsById,
+ Repositories.Contents.FindItemsById,
+ Repositories.Shortcuts.FindItemsById,
+ Repositories.Reminders.FindItemsById,
+ Repositories.Colors.FindItemsById,
+ Repositories.Tags.FindItemsById,
+ Repositories.Vaults.FindItemsById,
+ Repositories.Relations.FindItemsById,
+ ],
+ types: CollectionKeys,
+ userId,
+ ids,
+ size: 1000,
+ resetSync: isResetSync,
+ 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)
+ {
+ var syncedIds = chunk.Items.Select((i) => $"{i.ItemId}:{chunk.Type}").ToHashSet();
+ ids = ids.Where((id) => !syncedIds.Contains(id)).ToArray();
+ await SyncDeviceService.WritePendingIdsAsync(userId, deviceId, ids);
+ }
+ }
+
+ SyncDeviceService.Reset(userId, deviceId);
+
+ return new SyncV2Metadata
+ {
+ Synced = true,
+ };
+ }
+ }
+
+ [MessagePack.MessagePackObject]
+ public struct SyncV2Metadata
+ {
+ [MessagePack.Key("synced")]
+ [JsonPropertyName("synced")]
+ public bool Synced { get; set; }
+ }
+
+ [MessagePack.MessagePackObject]
+ public struct SyncV2TransferItem
+ {
+ [MessagePack.Key("items")]
+ [JsonPropertyName("items")]
+ public IEnumerable Items { get; set; }
+
+ [MessagePack.Key("type")]
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [MessagePack.Key("final")]
+ [JsonPropertyName("final")]
+ public bool Final { get; set; }
+
+ [MessagePack.Key("vaultKey")]
+ [JsonPropertyName("vaultKey")]
+ public EncryptedData VaultKey { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Notesnook.API/Repositories/SyncItemsRepository.cs b/Notesnook.API/Repositories/SyncItemsRepository.cs
index a2625d9..a44f719 100644
--- a/Notesnook.API/Repositories/SyncItemsRepository.cs
+++ b/Notesnook.API/Repositories/SyncItemsRepository.cs
@@ -24,6 +24,7 @@ using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using IdentityModel;
using Microsoft.VisualBasic;
using MongoDB.Bson;
using MongoDB.Driver;
@@ -68,6 +69,35 @@ namespace Notesnook.API.Repositories
Sort = new SortDefinitionBuilder().Ascending((a) => a.Id)
});
}
+
+ public Task> FindItemsById(string userId, IEnumerable ids, bool all, int batchSize)
+ {
+ var filters = new List>(new[] { Builders.Filter.Eq((i) => i.UserId, userId) });
+
+ if (!all) filters.Add(Builders.Filter.In((i) => i.ItemId, ids));
+
+ return Collection.FindAsync(Builders.Filter.And(filters), new FindOptions
+ {
+ BatchSize = batchSize,
+ AllowDiskUse = true,
+ AllowPartialResults = false,
+ NoCursorTimeout = true
+ });
+ }
+
+ public Task> GetIdsAsync(string userId, int batchSize)
+ {
+ var filter = Builders.Filter.Eq((i) => i.UserId, userId);
+ return Collection.FindAsync(filter, new FindOptions
+ {
+ BatchSize = batchSize,
+ AllowDiskUse = true,
+ AllowPartialResults = false,
+ NoCursorTimeout = true,
+ Sort = new SortDefinitionBuilder().Ascending((a) => a.Id),
+ Projection = Builders.Projection.Include((i) => i.ItemId)
+ });
+ }
// public async Task DeleteIdsAsync(string[] ids, string userId, CancellationToken token = default(CancellationToken))
// {
// await Collection.DeleteManyAsync((i) => ids.Contains(i.Id) && i.UserId == userId, token);
diff --git a/Notesnook.API/Services/SyncDeviceService.cs b/Notesnook.API/Services/SyncDeviceService.cs
new file mode 100644
index 0000000..85a7a3b
--- /dev/null
+++ b/Notesnook.API/Services/SyncDeviceService.cs
@@ -0,0 +1,168 @@
+/*
+This file is part of the Notesnook Sync Server project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the Affero GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Affero GNU General Public License for more details.
+
+You should have received a copy of the Affero GNU General Public License
+along with this program. If not, see .
+*/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Notesnook.API.Services
+{
+ public class SyncDeviceService
+ {
+ private static string UserSyncDirectoryPath(string userId) => Path.Join("sync", userId);
+ private static string UserDeviceDirectoryPath(string userId, string deviceId) => Path.Join(SyncDeviceService.UserSyncDirectoryPath(userId), deviceId);
+
+ private static string PendingIdsFilePath(string userId, string deviceId) => Path.Join(SyncDeviceService.UserDeviceDirectoryPath(userId, deviceId), "pending");
+
+ private static string UnsyncedIdsFilePath(string userId, string deviceId) => Path.Join(SyncDeviceService.UserDeviceDirectoryPath(userId, deviceId), "unsynced");
+
+ private static string ResetSyncFilePath(string userId, string deviceId) => Path.Join(SyncDeviceService.UserDeviceDirectoryPath(userId, deviceId), "reset-sync");
+
+ public static async Task GetUnsyncedIdsAsync(string userId, string deviceId)
+ {
+ try
+ {
+ return await File.ReadAllLinesAsync(UnsyncedIdsFilePath(userId, deviceId));
+ }
+ catch
+ {
+ return Array.Empty();
+ }
+ }
+
+ public static async Task FetchUnsyncedIdsAsync(string userId, string deviceId)
+ {
+ if (IsSyncReset(userId, deviceId)) return Array.Empty();
+ if (UnsyncedIdsFileLocks.TryGetValue(deviceId, out SemaphoreSlim fileLock) && fileLock.CurrentCount == 0)
+ await fileLock.WaitAsync();
+ try
+ {
+ var unsyncedIds = await GetUnsyncedIdsAsync(userId, deviceId);
+ if (IsSyncPending(userId, deviceId))
+ {
+ unsyncedIds = unsyncedIds.Union(await File.ReadAllLinesAsync(PendingIdsFilePath(userId, deviceId))).ToArray();
+ }
+
+ if (unsyncedIds.Length == 0) return Array.Empty();
+
+ File.Delete(UnsyncedIdsFilePath(userId, deviceId));
+ await File.WriteAllLinesAsync(PendingIdsFilePath(userId, deviceId), unsyncedIds);
+
+ return unsyncedIds;
+ }
+ catch
+ {
+ return Array.Empty();
+ }
+ finally
+ {
+ if (fileLock != null && fileLock.CurrentCount == 0) fileLock.Release();
+ }
+ }
+
+
+ public static async Task WritePendingIdsAsync(string userId, string deviceId, IEnumerable ids)
+ {
+ await File.WriteAllLinesAsync(PendingIdsFilePath(userId, deviceId), ids);
+ }
+
+ public static bool IsSyncReset(string userId, string deviceId)
+ {
+ return File.Exists(ResetSyncFilePath(userId, deviceId));
+ }
+
+ public static bool IsSyncPending(string userId, string deviceId)
+ {
+ return File.Exists(PendingIdsFilePath(userId, deviceId));
+ }
+
+ public static bool IsUnsynced(string userId, string deviceId)
+ {
+ return File.Exists(UnsyncedIdsFilePath(userId, deviceId));
+ }
+
+ public static void Reset(string userId, string deviceId)
+ {
+ File.Delete(ResetSyncFilePath(userId, deviceId));
+ File.Delete(PendingIdsFilePath(userId, deviceId));
+ }
+
+ public static bool IsDeviceRegistered(string userId, string deviceId)
+ {
+ return Directory.Exists(UserDeviceDirectoryPath(userId, deviceId));
+ }
+
+ public static IEnumerable ListDevices(string userId)
+ {
+ return Directory.EnumerateDirectories(UserSyncDirectoryPath(userId)).Select((path) => Path.GetFileName(path));
+ }
+
+ public static void ResetDevices(string userId)
+ {
+ if (File.Exists(UserSyncDirectoryPath(userId))) File.Delete(UserSyncDirectoryPath(userId));
+ Directory.CreateDirectory(UserSyncDirectoryPath(userId));
+ }
+
+ private static readonly Dictionary UnsyncedIdsFileLocks = new();
+ public static async Task AddIdsToOtherDevicesAsync(string userId, string deviceId, List ids)
+ {
+ foreach (var id in ListDevices(userId))
+ {
+ if (id == deviceId || IsSyncReset(userId, id)) continue;
+ if (!UnsyncedIdsFileLocks.TryGetValue(id, out SemaphoreSlim fileLock))
+ {
+ fileLock = new SemaphoreSlim(1, 1);
+ UnsyncedIdsFileLocks.Add(id, fileLock);
+ }
+
+ await fileLock.WaitAsync();
+ try
+ {
+ if (!IsDeviceRegistered(userId, id)) Directory.CreateDirectory(UserDeviceDirectoryPath(userId, id));
+
+ var oldIds = await GetUnsyncedIdsAsync(userId, id);
+ await File.WriteAllLinesAsync(UnsyncedIdsFilePath(userId, id), ids.Union(oldIds));
+ }
+ finally
+ {
+ fileLock.Release();
+ }
+ }
+
+ }
+
+ public static void RegisterDevice(string userId, string deviceId)
+ {
+ Directory.CreateDirectory(UserDeviceDirectoryPath(userId, deviceId));
+ File.Create(ResetSyncFilePath(userId, deviceId)).Close();
+ }
+
+ public static void UnregisterDevice(string userId, string deviceId)
+ {
+ try
+ {
+ Directory.Delete(UserDeviceDirectoryPath(userId, deviceId), true);
+ }
+ catch { }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Notesnook.API/Services/UserService.cs b/Notesnook.API/Services/UserService.cs
index 927efa7..c2581fc 100644
--- a/Notesnook.API/Services/UserService.cs
+++ b/Notesnook.API/Services/UserService.cs
@@ -174,40 +174,68 @@ namespace Notesnook.API.Services
public async Task DeleteUserAsync(string userId, string jti)
{
- var cc = new CancellationTokenSource();
+ try
+ {
+ await Slogger.Info(nameof(DeleteUserAsync), "Deleting user account", userId);
+ SyncDeviceService.ResetDevices(userId);
+
+ var cc = new CancellationTokenSource();
+
+ Repositories.Notes.DeleteByUserId(userId);
+ Repositories.Notebooks.DeleteByUserId(userId);
+ Repositories.Shortcuts.DeleteByUserId(userId);
+ Repositories.Contents.DeleteByUserId(userId);
Repositories.Settings.DeleteByUserId(userId);
Repositories.LegacySettings.DeleteByUserId(userId);
+ Repositories.Attachments.DeleteByUserId(userId);
+ Repositories.Reminders.DeleteByUserId(userId);
+ Repositories.Relations.DeleteByUserId(userId);
+ Repositories.Colors.DeleteByUserId(userId);
+ Repositories.Tags.DeleteByUserId(userId);
Repositories.Vaults.DeleteByUserId(userId);
+ Repositories.UsersSettings.Delete((u) => u.UserId == userId);
+ Repositories.Monographs.DeleteMany((m) => m.UserId == userId);
- if (!Constants.IS_SELF_HOSTED)
- {
- await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
- {
- AppId = ApplicationType.NOTESNOOK,
- UserId = userId
- });
- }
+ var result = await unit.Commit();
+ await Slogger.Info(nameof(DeleteUserAsync), "User account deleted", userId, result.ToString());
+ if (!result) return false;
- await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
- {
- SendToAll = false,
- OriginTokenId = jti,
- UserId = userId,
- Message = new Message
+ if (!Constants.IS_SELF_HOSTED)
{
- Type = "logout",
- Data = JsonSerializer.Serialize(new { reason = "Account deleted." })
+ await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
+ {
+ AppId = ApplicationType.NOTESNOOK,
+ UserId = userId
+ });
}
- });
- await S3Service.DeleteDirectoryAsync(userId);
+ await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
+ {
+ SendToAll = false,
+ OriginTokenId = jti,
+ UserId = userId,
+ Message = new Message
+ {
+ Type = "logout",
+ Data = JsonSerializer.Serialize(new { reason = "Account deleted." })
+ }
+ });
- return await unit.Commit();
+ await S3Service.DeleteDirectoryAsync(userId);
+ return result;
+ }
+ catch (Exception ex)
+ {
+ await Slogger.Error(nameof(DeleteUserAsync), "User account not deleted", userId, ex.ToString());
+ }
+ return false;
}
public async Task ResetUserAsync(string userId, bool removeAttachments)
{
+ SyncDeviceService.ResetDevices(userId);
+
var cc = new CancellationTokenSource();
Repositories.Notes.DeleteByUserId(userId);
@@ -229,7 +257,6 @@ namespace Notesnook.API.Services
userSettings.AttachmentsKey = null;
userSettings.VaultKey = null;
- userSettings.Profile = null;
userSettings.LastSynced = 0;
await Repositories.UsersSettings.UpsertAsync(userSettings, (s) => s.UserId == userId);
diff --git a/Notesnook.API/Startup.cs b/Notesnook.API/Startup.cs
index 0a055a7..fe1cfd6 100644
--- a/Notesnook.API/Startup.cs
+++ b/Notesnook.API/Startup.cs
@@ -160,41 +160,69 @@ namespace Notesnook.API
});
if (!BsonClassMap.IsClassMapRegistered(typeof(UserSettings)))
- {
BsonClassMap.RegisterClassMap();
- }
if (!BsonClassMap.IsClassMapRegistered(typeof(EncryptedData)))
- {
BsonClassMap.RegisterClassMap();
- }
if (!BsonClassMap.IsClassMapRegistered(typeof(CallToAction)))
- {
BsonClassMap.RegisterClassMap();
- }
- if (!BsonClassMap.IsClassMapRegistered(typeof(Announcement)))
- {
- BsonClassMap.RegisterClassMap();
- }
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Attachment)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Content)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Note)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Notebook)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Relation)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Reminder)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Setting)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(SettingItem)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Shortcut)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Tag)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Color)))
+ BsonClassMap.RegisterClassMap();
+
+ if (!BsonClassMap.IsClassMapRegistered(typeof(Vault)))
+ BsonClassMap.RegisterClassMap();
services.AddScoped();
services.AddScoped();
- services.AddScoped((provider) => new Repository(provider.GetRequiredService(), "notesnook", "user_settings"));
- services.AddScoped((provider) => new Repository(provider.GetRequiredService(), "notesnook", "monographs"));
- services.AddScoped((provider) => new Repository(provider.GetRequiredService(), "notesnook", "announcements"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "attachments"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "content"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "notes"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "notebooks"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "relations"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "reminders"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "settings"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "shortcuts"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "tags"));
- services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), "notesnook", "colors"));
+ services.AddRepository("user_settings")
+ .AddRepository("monographs")
+ .AddRepository("announcements");
+
+ services.AddSyncRepository("settingsv2")
+ .AddSyncRepository("attachments")
+ .AddSyncRepository("content")
+ .AddSyncRepository("notes")
+ .AddSyncRepository("notebooks")
+ .AddSyncRepository("relations")
+ .AddSyncRepository("reminders")
+ .AddSyncRepository("settings")
+ .AddSyncRepository("shortcuts")
+ .AddSyncRepository("tags")
+ .AddSyncRepository("colors")
+ .AddSyncRepository("vaults");
services.TryAddTransient();
services.TryAddTransient();
@@ -202,7 +230,7 @@ namespace Notesnook.API
services.AddControllers();
- services.AddHealthChecks().AddMongoDb(dbSettings.ConnectionString, dbSettings.DatabaseName, "database-check");
+ services.AddHealthChecks(); // .AddMongoDb(dbSettings.ConnectionString, dbSettings.DatabaseName, "database-check");
services.AddSignalR((hub) =>
{
hub.MaximumReceiveMessageSize = 100 * 1024 * 1024;
@@ -245,15 +273,17 @@ namespace Notesnook.API
app.UseWamp(WampServers.NotesnookServer, (realm, server) =>
{
- IUserService service = app.GetScopedService();
-
realm.Subscribe(IdentityServerTopics.DeleteUserTopic, async (ev) =>
{
+ IUserService service = app.GetScopedService();
await service.DeleteUserAsync(ev.UserId, null);
});
- IDistributedCache cache = app.GetScopedService();
- realm.Subscribe(IdentityServerTopics.ClearCacheTopic, (ev) => ev.Keys.ForEach((key) => cache.Remove(key)));
+ realm.Subscribe(IdentityServerTopics.ClearCacheTopic, (ev) =>
+ {
+ IDistributedCache cache = app.GetScopedService();
+ ev.Keys.ForEach((key) => cache.Remove(key));
+ });
});
app.UseRouting();
@@ -270,7 +300,27 @@ namespace Notesnook.API
options.CloseOnAuthenticationExpiration = false;
options.Transports = HttpTransportType.WebSockets;
});
+ endpoints.MapHub("/hubs/sync/v2", options =>
+ {
+ options.CloseOnAuthenticationExpiration = false;
+ options.Transports = HttpTransportType.WebSockets;
+ });
});
}
}
+
+ public static class ServiceCollectionRepositoryExtensions
+ {
+ public static IServiceCollection AddRepository(this IServiceCollection services, string collectionName, string database = "notesnook") where T : class
+ {
+ services.AddScoped((provider) => new Repository(provider.GetRequiredService(), database, collectionName));
+ return services;
+ }
+
+ public static IServiceCollection AddSyncRepository(this IServiceCollection services, string collectionName, string database = "notesnook") where T : SyncItem
+ {
+ services.AddScoped((provider) => new SyncItemsRepository(provider.GetRequiredService(), database, collectionName));
+ return services;
+ }
+ }
}