diff --git a/Notesnook.API/Controllers/MonographsController.cs b/Notesnook.API/Controllers/MonographsController.cs index d37e68e..4a08439 100644 --- a/Notesnook.API/Controllers/MonographsController.cs +++ b/Notesnook.API/Controllers/MonographsController.cs @@ -24,8 +24,10 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using MongoDB.Bson; using MongoDB.Driver; using Notesnook.API.Models; +using Streetwriters.Common; using Streetwriters.Data.Interfaces; using Streetwriters.Data.Repositories; @@ -46,53 +48,121 @@ namespace Notesnook.API.Controllers unit = unitOfWork; } + private static FilterDefinition CreateMonographFilter(string userId, Monograph monograph) + { + var userIdFilter = Builders.Filter.Eq("UserId", userId); + return ObjectId.TryParse(monograph.ItemId, out ObjectId id) + ? Builders.Filter + .And(userIdFilter, + Builders.Filter.Or( + Builders.Filter.Eq("_id", id), Builders.Filter.Eq("ItemId", monograph.ItemId) + ) + ) + : Builders.Filter + .And(userIdFilter, + Builders.Filter.Eq("ItemId", monograph.ItemId) + ); + } + + private static FilterDefinition CreateMonographFilter(string itemId) + { + return ObjectId.TryParse(itemId, out ObjectId id) + ? Builders.Filter.Or( + Builders.Filter.Eq("_id", id), + Builders.Filter.Eq("ItemId", itemId)) + : Builders.Filter.Eq("ItemId", itemId); + } + + private async Task FindMonographAsync(string userId, Monograph monograph) + { + var result = await Monographs.Collection.FindAsync(CreateMonographFilter(userId, monograph), new FindOptions + { + Limit = 1 + }); + return await result.FirstOrDefaultAsync(); + } + + private async Task FindMonographAsync(string itemId) + { + var result = await Monographs.Collection.FindAsync(CreateMonographFilter(itemId), new FindOptions + { + Limit = 1 + }); + return await result.FirstOrDefaultAsync(); + } + [HttpPost] public async Task PublishAsync([FromBody] Monograph monograph) { - var userId = this.User.FindFirstValue("sub"); - if (userId == null) return Unauthorized(); - - if (await Monographs.GetAsync(monograph.Id) != null) return base.Conflict("This monograph is already published."); - - if (monograph.EncryptedContent == null) - monograph.CompressedContent = monograph.Content.CompressBrotli(); - monograph.UserId = userId; - monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - - - 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."); - - Monographs.Insert(monograph); - - if (!await unit.Commit()) return BadRequest(); - return Ok(new + try { - id = monograph.Id - }); + 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."); + + if (monograph.EncryptedContent == null) + monograph.CompressedContent = monograph.Content.CompressBrotli(); + monograph.UserId = userId; + monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + 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); + + return Ok(new + { + id = monograph.ItemId + }); + } + catch (Exception e) + { + await Slogger.Error(nameof(PublishAsync), e.ToString()); + return BadRequest(); + } } [HttpPatch] public async Task UpdateAsync([FromBody] Monograph monograph) { - if (await Monographs.GetAsync(monograph.Id) == null) 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."); - - if (monograph.EncryptedContent == null) - monograph.CompressedContent = monograph.Content.CompressBrotli(); - else - monograph.Content = null; - - monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - Monographs.Update(monograph.Id, monograph); - - if (!await unit.Commit()) return BadRequest(); - return Ok(new + try { - id = monograph.Id - }); + var userId = this.User.FindFirstValue("sub"); + if (userId == null) return Unauthorized(); + + if (await FindMonographAsync(userId, monograph) == null) 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."); + + if (monograph.EncryptedContent == null) + monograph.CompressedContent = monograph.Content.CompressBrotli(); + else + monograph.Content = null; + + monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var result = await Monographs.Collection.UpdateOneAsync( + CreateMonographFilter(userId, monograph), + Builders.Update + .Set(m => m.DatePublished, monograph.DatePublished) + .Set(m => m.CompressedContent, monograph.CompressedContent) + .Set(m => m.EncryptedContent, monograph.EncryptedContent) + .Set(m => m.SelfDestruct, monograph.SelfDestruct) + .Set(m => m.Title, monograph.Title) + ); + if (!result.IsAcknowledged) return BadRequest(); + + return Ok(new + { + id = monograph.ItemId + }); + } + catch (Exception e) + { + await Slogger.Error(nameof(UpdateAsync), e.ToString()); + return BadRequest(); + } } [HttpGet] @@ -103,17 +173,16 @@ namespace Notesnook.API.Controllers var monographs = (await Monographs.Collection.FindAsync(Builders.Filter.Eq("UserId", userId), new FindOptions { - Projection = Builders.Projection.Include("_id"), + Projection = Builders.Projection.Include("_id").Include("ItemId"), })).ToEnumerable(); - return Ok(monographs.Select((m) => m.Id)); + return Ok(monographs.Select((m) => m.ItemId ?? m.Id)); } - [HttpGet("{id}")] [AllowAnonymous] public async Task GetMonographAsync([FromRoute] string id) { - var monograph = await Monographs.GetAsync(id); + var monograph = await FindMonographAsync(id); if (monograph == null) { return NotFound(new @@ -125,6 +194,7 @@ namespace Notesnook.API.Controllers if (monograph.EncryptedContent == null) monograph.Content = monograph.CompressedContent.DecompressBrotli(); + if (monograph.ItemId == null) monograph.ItemId = monograph.Id; return Ok(monograph); } @@ -132,7 +202,7 @@ namespace Notesnook.API.Controllers [AllowAnonymous] public async Task TrackView([FromRoute] string id) { - var monograph = await Monographs.GetAsync(id); + var monograph = await FindMonographAsync(id); if (monograph == null) return Content(SVG_PIXEL, "image/svg+xml"); if (monograph.SelfDestruct) @@ -144,8 +214,7 @@ namespace Notesnook.API.Controllers [HttpDelete("{id}")] public async Task DeleteAsync([FromRoute] string id) { - Monographs.DeleteById(id); - if (!await unit.Commit()) return BadRequest(); + await Monographs.Collection.DeleteOneAsync(CreateMonographFilter(id)); return Ok(); } } diff --git a/Notesnook.API/Models/Monograph.cs b/Notesnook.API/Models/Monograph.cs index edb9e92..bae6067 100644 --- a/Notesnook.API/Models/Monograph.cs +++ b/Notesnook.API/Models/Monograph.cs @@ -1,67 +1,90 @@ -/* -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.Text.Json.Serialization; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; -using Notesnook.API.Interfaces; - -namespace Notesnook.API.Models -{ - public class ObjectWithId - { - [BsonId] - [BsonRepresentation(BsonType.ObjectId)] - public string Id { get; set; } - } - - public class Monograph : IMonograph - { - public Monograph() - { - Id = ObjectId.GenerateNewId().ToString(); - } - - [BsonId] - [BsonRepresentation(BsonType.ObjectId)] - public string Id { get; set; } - - [JsonPropertyName("title")] - public string Title { get; set; } - - [JsonPropertyName("userId")] - public string UserId { get; set; } - - [JsonPropertyName("selfDestruct")] - public bool SelfDestruct { get; set; } - - [JsonPropertyName("encryptedContent")] - public EncryptedData EncryptedContent { get; set; } - - [JsonPropertyName("datePublished")] - public long DatePublished { get; set; } - - [JsonPropertyName("content")] - [BsonIgnore] - public string Content { get; set; } - - [JsonIgnore] - public byte[] CompressedContent { get; set; } - } +/* +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.Text.Json.Serialization; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using System.Runtime.Serialization; + +namespace Notesnook.API.Models +{ + public class ObjectWithId + { + [BsonId] + [BsonIgnoreIfDefault] + [BsonRepresentation(BsonType.ObjectId)] + public string Id + { + get; set; + } + + public string ItemId + { + get; set; + } + } + + public class Monograph + { + public Monograph() + { + Id = ObjectId.GenerateNewId().ToString(); + } + + [DataMember(Name = "id")] + [JsonPropertyName("id")] + [MessagePack.Key("id")] + public string ItemId + { + get; set; + } + + [BsonId] + [BsonIgnoreIfDefault] + [BsonRepresentation(BsonType.ObjectId)] + [JsonIgnore] + [MessagePack.IgnoreMember] + public string Id + { + get; set; + } + + [JsonPropertyName("title")] + public string Title { get; set; } + + [JsonPropertyName("userId")] + public string UserId { get; set; } + + [JsonPropertyName("selfDestruct")] + public bool SelfDestruct { get; set; } + + [JsonPropertyName("encryptedContent")] + public EncryptedData EncryptedContent { get; set; } + + [JsonPropertyName("datePublished")] + public long DatePublished { get; set; } + + [JsonPropertyName("content")] + [BsonIgnore] + public string Content { get; set; } + + [JsonIgnore] + public byte[] CompressedContent { get; set; } + } } \ No newline at end of file