diff --git a/Notesnook.API/Controllers/MonographsController.cs b/Notesnook.API/Controllers/MonographsController.cs
index 4a08439..77a27a1 100644
--- a/Notesnook.API/Controllers/MonographsController.cs
+++ b/Notesnook.API/Controllers/MonographsController.cs
@@ -18,16 +18,19 @@ along with this program. If not, see .
*/
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
-using System.Text.RegularExpressions;
+using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using MongoDB.Driver;
using Notesnook.API.Models;
+using Notesnook.API.Services;
using Streetwriters.Common;
+using Streetwriters.Common.Messages;
using Streetwriters.Data.Interfaces;
using Streetwriters.Data.Repositories;
@@ -92,14 +95,18 @@ namespace Notesnook.API.Controllers
}
[HttpPost]
- public async Task PublishAsync([FromBody] Monograph monograph)
+ public async Task PublishAsync([FromQuery] string deviceId, [FromBody] Monograph monograph)
{
try
{
var userId = this.User.FindFirstValue("sub");
if (userId == null) return Unauthorized();
- if (await FindMonographAsync(userId, monograph) != null) return base.Conflict("This monograph is already published.");
+ var existingMonograph = await FindMonographAsync(userId, monograph);
+ if (existingMonograph != null && !existingMonograph.Deleted)
+ {
+ return base.Conflict("This monograph is already published.");
+ }
if (monograph.EncryptedContent == null)
monograph.CompressedContent = monograph.Content.CompressBrotli();
@@ -109,11 +116,23 @@ namespace Notesnook.API.Controllers
if (monograph.EncryptedContent?.Cipher.Length > MAX_DOC_SIZE || monograph.CompressedContent?.Length > MAX_DOC_SIZE)
return base.BadRequest("Monograph is too big. Max allowed size is 15mb.");
- await Monographs.InsertAsync(monograph);
+ if (existingMonograph != null)
+ {
+ monograph.Id = existingMonograph?.Id;
+ }
+ monograph.Deleted = false;
+ await Monographs.Collection.ReplaceOneAsync(
+ CreateMonographFilter(userId, monograph),
+ monograph,
+ new ReplaceOptions { IsUpsert = true }
+ );
+
+ await MarkMonographForSyncAsync(monograph.ItemId ?? monograph.Id, deviceId);
return Ok(new
{
- id = monograph.ItemId
+ id = monograph.ItemId,
+ datePublished = monograph.DatePublished,
});
}
catch (Exception e)
@@ -124,14 +143,18 @@ namespace Notesnook.API.Controllers
}
[HttpPatch]
- public async Task UpdateAsync([FromBody] Monograph monograph)
+ public async Task UpdateAsync([FromQuery] string deviceId, [FromBody] Monograph monograph)
{
try
{
var userId = this.User.FindFirstValue("sub");
if (userId == null) return Unauthorized();
- if (await FindMonographAsync(userId, monograph) == null) return NotFound();
+ var existingMonograph = await FindMonographAsync(userId, monograph);
+ if (existingMonograph != null || existingMonograph.Deleted)
+ {
+ return NotFound();
+ }
if (monograph.EncryptedContent?.Cipher.Length > MAX_DOC_SIZE || monograph.CompressedContent?.Length > MAX_DOC_SIZE)
return base.BadRequest("Monograph is too big. Max allowed size is 15mb.");
@@ -150,12 +173,16 @@ namespace Notesnook.API.Controllers
.Set(m => m.EncryptedContent, monograph.EncryptedContent)
.Set(m => m.SelfDestruct, monograph.SelfDestruct)
.Set(m => m.Title, monograph.Title)
+ .Set(m => m.Password, monograph.Password)
);
if (!result.IsAcknowledged) return BadRequest();
+ await MarkMonographForSyncAsync(monograph.ItemId ?? monograph.Id, deviceId);
+
return Ok(new
{
- id = monograph.ItemId
+ id = monograph.ItemId,
+ datePublished = monograph.DatePublished,
});
}
catch (Exception e)
@@ -171,10 +198,15 @@ namespace Notesnook.API.Controllers
var userId = this.User.FindFirstValue("sub");
if (userId == null) return Unauthorized();
- var monographs = (await Monographs.Collection.FindAsync(Builders.Filter.Eq("UserId", userId), new FindOptions
- {
- Projection = Builders.Projection.Include("_id").Include("ItemId"),
- })).ToEnumerable();
+ var monographs = (await Monographs.Collection.FindAsync(
+ Builders.Filter.And(
+ Builders.Filter.Eq("UserId", userId),
+ Builders.Filter.Eq("Deleted", false)
+ )
+ , new FindOptions
+ {
+ Projection = Builders.Projection.Include("_id").Include("ItemId"),
+ })).ToEnumerable();
return Ok(monographs.Select((m) => m.ItemId ?? m.Id));
}
@@ -183,7 +215,7 @@ namespace Notesnook.API.Controllers
public async Task GetMonographAsync([FromRoute] string id)
{
var monograph = await FindMonographAsync(id);
- if (monograph == null)
+ if (monograph == null || monograph.Deleted)
{
return NotFound(new
{
@@ -203,19 +235,89 @@ namespace Notesnook.API.Controllers
public async Task TrackView([FromRoute] string id)
{
var monograph = await FindMonographAsync(id);
- if (monograph == null) return Content(SVG_PIXEL, "image/svg+xml");
+ if (monograph == null || monograph.Deleted) return Content(SVG_PIXEL, "image/svg+xml");
if (monograph.SelfDestruct)
- await Monographs.DeleteByIdAsync(monograph.Id);
+ {
+ var userId = this.User.FindFirstValue("sub");
+ await Monographs.Collection.ReplaceOneAsync(
+ CreateMonographFilter(userId, monograph),
+ new Monograph
+ {
+ ItemId = id,
+ Id = monograph.Id,
+ Deleted = true
+ }
+ );
+
+ await MarkMonographForSyncAsync(id);
+ }
return Content(SVG_PIXEL, "image/svg+xml");
}
[HttpDelete("{id}")]
- public async Task DeleteAsync([FromRoute] string id)
+ public async Task DeleteAsync([FromQuery] string deviceId, [FromRoute] string id)
{
- await Monographs.Collection.DeleteOneAsync(CreateMonographFilter(id));
+ var monograph = await FindMonographAsync(id);
+ if (monograph == null || monograph.Deleted)
+ {
+ return NotFound(new
+ {
+ error = "invalid_id",
+ error_description = $"No such monograph found."
+ });
+ }
+
+ var userId = this.User.FindFirstValue("sub");
+ await Monographs.Collection.ReplaceOneAsync(
+ CreateMonographFilter(userId, monograph),
+ new Monograph
+ {
+ ItemId = id,
+ Id = monograph.Id,
+ Deleted = true,
+ UserId = monograph.UserId
+ }
+ );
+
+ await MarkMonographForSyncAsync(id, deviceId);
+
return Ok();
}
+
+ private async Task MarkMonographForSyncAsync(string monographId, string deviceId)
+ {
+ if (deviceId == null) return;
+ var userId = this.User.FindFirstValue("sub");
+
+ new SyncDeviceService(new SyncDevice(userId, deviceId)).AddIdsToOtherDevices([$"{monographId}:monograph"]);
+ await SendTriggerSyncEventAsync();
+ }
+
+ private async Task MarkMonographForSyncAsync(string monographId)
+ {
+ var userId = this.User.FindFirstValue("sub");
+
+ new SyncDeviceService(new SyncDevice(userId, string.Empty)).AddIdsToAllDevices([$"{monographId}:monograph"]);
+ await SendTriggerSyncEventAsync(sendToAllDevices: true);
+ }
+
+ private async Task SendTriggerSyncEventAsync(bool sendToAllDevices = false)
+ {
+ var userId = this.User.FindFirstValue("sub");
+ var jti = this.User.FindFirstValue("jti");
+
+ 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." })
+ }
+ });
+ }
}
}
\ No newline at end of file
diff --git a/Notesnook.API/Hubs/SyncV2Hub.cs b/Notesnook.API/Hubs/SyncV2Hub.cs
index 8b62d92..819c23f 100644
--- a/Notesnook.API/Hubs/SyncV2Hub.cs
+++ b/Notesnook.API/Hubs/SyncV2Hub.cs
@@ -20,7 +20,6 @@ along with this program. If not, see .
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Diagnostics.Metrics;
using System.Linq;
using System.Security.Claims;
using System.Text.Json.Serialization;
@@ -28,8 +27,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
-using MongoDB.Bson;
-using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using Notesnook.API.Authorization;
using Notesnook.API.Interfaces;
@@ -43,6 +40,7 @@ namespace Notesnook.API.Hubs
{
Task SendItems(SyncTransferItemV2 transferItem);
Task SendVaultKey(EncryptedData vaultKey);
+ Task SendMonographs(IEnumerable monographs);
Task PushCompleted();
}
@@ -259,6 +257,7 @@ namespace Notesnook.API.Hubs
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.");
@@ -271,6 +270,15 @@ namespace Notesnook.API.Hubs
}
}
+ var unsyncedMonographs = ids.Where((id) => id.EndsWith(":monograph")).ToHashSet();
+ var unsyncedMonographIds = unsyncedMonographs.Select((id) => id.Split(":")[0]).ToArray();
+ var userMonographs = isResetSync
+ ? await Repositories.Monographs.FindAsync(m => m.UserId == userId)
+ : await Repositories.Monographs.FindAsync(m => m.UserId == userId && unsyncedMonographIds.Contains(m.ItemId));
+
+ if (userMonographs.Any() && !await Clients.Caller.SendMonographs(userMonographs).WaitAsync(TimeSpan.FromMinutes(10)))
+ throw new HubException("Client rejected monographs.");
+
deviceService.Reset();
return new SyncV2Metadata
diff --git a/Notesnook.API/Models/Monograph.cs b/Notesnook.API/Models/Monograph.cs
index bae6067..1830e4c 100644
--- a/Notesnook.API/Models/Monograph.cs
+++ b/Notesnook.API/Models/Monograph.cs
@@ -86,5 +86,11 @@ namespace Notesnook.API.Models
[JsonIgnore]
public byte[] CompressedContent { get; set; }
+
+ [JsonPropertyName("password")]
+ public EncryptedData Password { get; set; }
+
+ [JsonPropertyName("deleted")]
+ public bool Deleted { get; set; }
}
}
\ No newline at end of file
diff --git a/Notesnook.API/Services/SyncDeviceService.cs b/Notesnook.API/Services/SyncDeviceService.cs
index d627e64..2c1bf3d 100644
--- a/Notesnook.API/Services/SyncDeviceService.cs
+++ b/Notesnook.API/Services/SyncDeviceService.cs
@@ -194,6 +194,21 @@ namespace Notesnook.API.Services
}
}
+ public void AddIdsToAllDevices(List ids)
+ {
+ foreach (var id in ListDevices())
+ {
+ if (IsSyncReset(id)) return;
+ lock (id)
+ {
+ 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));
+ }
+ }
+ }
+
public void RegisterDevice()
{
lock (device.UserId)