monograph: add new ItemId property that is a simple string

This fixes the issue where an invalid objectid causes error when publishing a monograph
This commit is contained in:
Abdullah Atta
2024-11-28 14:21:57 +05:00
parent 07675632e0
commit c6bcd4a84d
2 changed files with 202 additions and 110 deletions

View File

@@ -24,8 +24,10 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Notesnook.API.Models; using Notesnook.API.Models;
using Streetwriters.Common;
using Streetwriters.Data.Interfaces; using Streetwriters.Data.Interfaces;
using Streetwriters.Data.Repositories; using Streetwriters.Data.Repositories;
@@ -46,53 +48,121 @@ namespace Notesnook.API.Controllers
unit = unitOfWork; unit = unitOfWork;
} }
private static FilterDefinition<Monograph> CreateMonographFilter(string userId, Monograph monograph)
{
var userIdFilter = Builders<Monograph>.Filter.Eq("UserId", userId);
return ObjectId.TryParse(monograph.ItemId, out ObjectId id)
? Builders<Monograph>.Filter
.And(userIdFilter,
Builders<Monograph>.Filter.Or(
Builders<Monograph>.Filter.Eq("_id", id), Builders<Monograph>.Filter.Eq("ItemId", monograph.ItemId)
)
)
: Builders<Monograph>.Filter
.And(userIdFilter,
Builders<Monograph>.Filter.Eq("ItemId", monograph.ItemId)
);
}
private static FilterDefinition<Monograph> CreateMonographFilter(string itemId)
{
return ObjectId.TryParse(itemId, out ObjectId id)
? Builders<Monograph>.Filter.Or(
Builders<Monograph>.Filter.Eq("_id", id),
Builders<Monograph>.Filter.Eq("ItemId", itemId))
: Builders<Monograph>.Filter.Eq("ItemId", itemId);
}
private async Task<Monograph> FindMonographAsync(string userId, Monograph monograph)
{
var result = await Monographs.Collection.FindAsync(CreateMonographFilter(userId, monograph), new FindOptions<Monograph>
{
Limit = 1
});
return await result.FirstOrDefaultAsync();
}
private async Task<Monograph> FindMonographAsync(string itemId)
{
var result = await Monographs.Collection.FindAsync(CreateMonographFilter(itemId), new FindOptions<Monograph>
{
Limit = 1
});
return await result.FirstOrDefaultAsync();
}
[HttpPost] [HttpPost]
public async Task<IActionResult> PublishAsync([FromBody] Monograph monograph) public async Task<IActionResult> PublishAsync([FromBody] Monograph monograph)
{ {
var userId = this.User.FindFirstValue("sub"); try
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
{ {
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<MonographsController>.Error(nameof(PublishAsync), e.ToString());
return BadRequest();
}
} }
[HttpPatch] [HttpPatch]
public async Task<IActionResult> UpdateAsync([FromBody] Monograph monograph) public async Task<IActionResult> UpdateAsync([FromBody] Monograph monograph)
{ {
if (await Monographs.GetAsync(monograph.Id) == null) return NotFound(); try
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
{ {
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<Monograph>.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<MonographsController>.Error(nameof(UpdateAsync), e.ToString());
return BadRequest();
}
} }
[HttpGet] [HttpGet]
@@ -103,17 +173,16 @@ namespace Notesnook.API.Controllers
var monographs = (await Monographs.Collection.FindAsync(Builders<Monograph>.Filter.Eq("UserId", userId), new FindOptions<Monograph, ObjectWithId> var monographs = (await Monographs.Collection.FindAsync(Builders<Monograph>.Filter.Eq("UserId", userId), new FindOptions<Monograph, ObjectWithId>
{ {
Projection = Builders<Monograph>.Projection.Include("_id"), Projection = Builders<Monograph>.Projection.Include("_id").Include("ItemId"),
})).ToEnumerable(); })).ToEnumerable();
return Ok(monographs.Select((m) => m.Id)); return Ok(monographs.Select((m) => m.ItemId ?? m.Id));
} }
[HttpGet("{id}")] [HttpGet("{id}")]
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> GetMonographAsync([FromRoute] string id) public async Task<IActionResult> GetMonographAsync([FromRoute] string id)
{ {
var monograph = await Monographs.GetAsync(id); var monograph = await FindMonographAsync(id);
if (monograph == null) if (monograph == null)
{ {
return NotFound(new return NotFound(new
@@ -125,6 +194,7 @@ namespace Notesnook.API.Controllers
if (monograph.EncryptedContent == null) if (monograph.EncryptedContent == null)
monograph.Content = monograph.CompressedContent.DecompressBrotli(); monograph.Content = monograph.CompressedContent.DecompressBrotli();
if (monograph.ItemId == null) monograph.ItemId = monograph.Id;
return Ok(monograph); return Ok(monograph);
} }
@@ -132,7 +202,7 @@ namespace Notesnook.API.Controllers
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> TrackView([FromRoute] string id) public async Task<IActionResult> 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 == null) return Content(SVG_PIXEL, "image/svg+xml");
if (monograph.SelfDestruct) if (monograph.SelfDestruct)
@@ -144,8 +214,7 @@ namespace Notesnook.API.Controllers
[HttpDelete("{id}")] [HttpDelete("{id}")]
public async Task<IActionResult> DeleteAsync([FromRoute] string id) public async Task<IActionResult> DeleteAsync([FromRoute] string id)
{ {
Monographs.DeleteById(id); await Monographs.Collection.DeleteOneAsync(CreateMonographFilter(id));
if (!await unit.Commit()) return BadRequest();
return Ok(); return Ok();
} }
} }

View File

@@ -1,67 +1,90 @@
/* /*
This file is part of the Notesnook Sync Server project (https://notesnook.com/) This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify 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 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 the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details. Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using Notesnook.API.Interfaces; using System.Runtime.Serialization;
namespace Notesnook.API.Models namespace Notesnook.API.Models
{ {
public class ObjectWithId public class ObjectWithId
{ {
[BsonId] [BsonId]
[BsonRepresentation(BsonType.ObjectId)] [BsonIgnoreIfDefault]
public string Id { get; set; } [BsonRepresentation(BsonType.ObjectId)]
} public string Id
{
public class Monograph : IMonograph get; set;
{ }
public Monograph()
{ public string ItemId
Id = ObjectId.GenerateNewId().ToString(); {
} get; set;
}
[BsonId] }
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } public class Monograph
{
[JsonPropertyName("title")] public Monograph()
public string Title { get; set; } {
Id = ObjectId.GenerateNewId().ToString();
[JsonPropertyName("userId")] }
public string UserId { get; set; }
[DataMember(Name = "id")]
[JsonPropertyName("selfDestruct")] [JsonPropertyName("id")]
public bool SelfDestruct { get; set; } [MessagePack.Key("id")]
public string ItemId
[JsonPropertyName("encryptedContent")] {
public EncryptedData EncryptedContent { get; set; } get; set;
}
[JsonPropertyName("datePublished")]
public long DatePublished { get; set; } [BsonId]
[BsonIgnoreIfDefault]
[JsonPropertyName("content")] [BsonRepresentation(BsonType.ObjectId)]
[BsonIgnore] [JsonIgnore]
public string Content { get; set; } [MessagePack.IgnoreMember]
public string Id
[JsonIgnore] {
public byte[] CompressedContent { get; set; } 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; }
}
} }