Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 58 additions & 9 deletions Notesnook.API/Controllers/MonographsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,25 @@ You should have received a copy of the Affero GNU General Public License
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using AngleSharp;
using AngleSharp.Dom;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using Notesnook.API.Authorization;
using NanoidDotNet;
using Notesnook.API.Extensions;
using Notesnook.API.Models;
using Notesnook.API.Services;
using Streetwriters.Common;
using Streetwriters.Common.Helpers;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Messages;
using Streetwriters.Data.Interfaces;
using Streetwriters.Data.Repositories;

namespace Notesnook.API.Controllers
Expand Down Expand Up @@ -95,6 +93,22 @@ private async Task<Monograph> FindMonographAsync(string itemId)
return await result.FirstOrDefaultAsync();
}

private async Task<Monograph> FindMonographBySlugAsync(string slug)
{
var result = await monographs.Collection.FindAsync(
Builders<Monograph>.Filter.Eq("Slug", slug),
new FindOptions<Monograph>
{
Limit = 1
});
return await result.FirstOrDefaultAsync();
}

private static string GenerateSlug()
{
return Nanoid.Generate(size: 24);
}

[HttpPost]
public async Task<IActionResult> PublishAsync([FromQuery] string? deviceId, [FromBody] Monograph monograph)
{
Expand All @@ -120,6 +134,7 @@ public async Task<IActionResult> PublishAsync([FromQuery] string? deviceId, [Fro
}
monograph.Deleted = false;
monograph.ViewCount = 0;
monograph.Slug = GenerateSlug();
await monographs.Collection.ReplaceOneAsync(
CreateMonographFilter(userId, monograph),
monograph,
Expand All @@ -131,7 +146,8 @@ await monographs.Collection.ReplaceOneAsync(
return Ok(new
{
id = monograph.ItemId,
datePublished = monograph.DatePublished
datePublished = monograph.DatePublished,
publishUrl = monograph.ConstructPublishUrl()
});
}
catch (Exception e)
Expand Down Expand Up @@ -164,6 +180,7 @@ public async Task<IActionResult> UpdateAsync([FromQuery] string? deviceId, [From
monograph.Content = null;

monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
monograph.Slug = GenerateSlug();
var result = await monographs.Collection.UpdateOneAsync(
CreateMonographFilter(userId, monograph),
Builders<Monograph>.Update
Expand All @@ -172,6 +189,7 @@ public async Task<IActionResult> UpdateAsync([FromQuery] string? deviceId, [From
.Set(m => m.EncryptedContent, monograph.EncryptedContent)
.Set(m => m.SelfDestruct, monograph.SelfDestruct)
.Set(m => m.Title, monograph.Title)
.Set(m => m.Slug, monograph.Slug)
.Set(m => m.Password, monograph.Password)
);
if (!result.IsAcknowledged) return BadRequest();
Expand All @@ -181,7 +199,8 @@ public async Task<IActionResult> UpdateAsync([FromQuery] string? deviceId, [From
return Ok(new
{
id = monograph.ItemId,
datePublished = monograph.DatePublished
datePublished = monograph.DatePublished,
publishUrl = monograph.ConstructPublishUrl()
});
}
catch (Exception e)
Expand All @@ -208,11 +227,25 @@ public async Task<IActionResult> GetUserMonographsAsync()
return Ok(userMonographs.Select((m) => m.ItemId ?? m.Id));
}

[HttpGet("{id}")]
[HttpGet("{slugOrId}")]
[AllowAnonymous]
public async Task<IActionResult> GetMonographAsync([FromRoute] string id)
public async Task<IActionResult> GetMonographAsync([FromRoute] string slugOrId)
{
var monograph = await FindMonographAsync(id);
var monograph = await FindMonographBySlugAsync(slugOrId);

if (monograph == null)
{
monograph = await FindMonographAsync(slugOrId);
if (!string.IsNullOrEmpty(monograph?.Slug))
{
return NotFound(new
{
error = "invalid_id",
error_description = $"No such monograph found."
});
}
Comment on lines +239 to +246
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still contemplating if we need to add this check. On one hand, I think it makes sense to restrict fetching monograph by id if the slug exists. On another hand, if a user's client app is a bit behind (i.e. doesn't have the slug property yet), they'll be shown the monograph link with the id which will be a 404 causing confusion

}

if (monograph == null || monograph.Deleted)
{
return NotFound(new
Expand Down Expand Up @@ -317,6 +350,22 @@ await monographs.Collection.ReplaceOneAsync(
return Ok();
}

[HttpGet("{id}/publish-url")]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can combine analytics and publish-url endpoints into one?

public async Task<IActionResult> GetPublishUrlAsync([FromRoute] string id)
{
var userId = this.User.GetUserId();
var monograph = await FindMonographAsync(id);
if (monograph == null || monograph.Deleted || monograph.UserId != userId)
{
return NotFound();
}

return Ok(new
{
publishUrl = monograph.ConstructPublishUrl()
});
}

private static async Task MarkMonographForSyncAsync(string userId, string monographId, string? deviceId, string? jti)
{
if (deviceId == null) return;
Expand Down
33 changes: 33 additions & 0 deletions Notesnook.API/Extensions/MonographExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
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 <http://www.gnu.org/licenses/>.
*/

using Notesnook.API.Models;
using Streetwriters.Common;

namespace Notesnook.API.Extensions
{
public static class MonographExtensions
{
public static string ConstructPublishUrl(this Monograph monograph)
{
var baseUrl = Constants.MONOGRAPH_PUBLIC_URL;
return $"{baseUrl}/{monograph.Slug ?? monograph.ItemId}";
}
}
}
9 changes: 6 additions & 3 deletions Notesnook.API/Hubs/SyncV2Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ You should have received a copy of the Affero GNU General Public License
using Microsoft.AspNetCore.SignalR;
using MongoDB.Driver;
using Notesnook.API.Authorization;
using Notesnook.API.Extensions;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Notesnook.API.Services;
Expand Down Expand Up @@ -275,18 +276,20 @@ private async Task<SyncV2Metadata> HandleRequestFetch(string deviceId, bool incl
Builders<Monograph>.Filter.In("_id", unsyncedMonographIds)
)
);
var userMonographs = await Repositories.Monographs.Collection.Find(filter).Project((m) => new MonographMetadata
var userMonographs = await Repositories.Monographs.Collection.Find(filter).ToListAsync();
var userMonographMetadatas = userMonographs.Select((m) => new MonographMetadata
{
DatePublished = m.DatePublished,
Deleted = m.Deleted,
Password = m.Password,
SelfDestruct = m.SelfDestruct,
Title = m.Title,
PublishUrl = m.ConstructPublishUrl(),
ItemId = m.ItemId ?? m.Id.ToString(),
ViewCount = m.ViewCount
}).ToListAsync();
}).ToList();

if (userMonographs.Count > 0 && !await Clients.Caller.SendMonographs(userMonographs).WaitAsync(TimeSpan.FromMinutes(10)))
if (userMonographMetadatas.Count > 0 && !await Clients.Caller.SendMonographs(userMonographMetadatas).WaitAsync(TimeSpan.FromMinutes(10)))
Comment on lines -278 to +292
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was unable to use m.ConstructPublishUrl method inside MongoDB's Project. It would throw expression not supported exception

throw new HubException("Client rejected monographs.");

device.HasInitialMonographsSync = true;
Expand Down
3 changes: 3 additions & 0 deletions Notesnook.API/Models/Monograph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public Monograph()
[JsonPropertyName("title")]
public string? Title { get; set; }

[JsonPropertyName("slug")]
public string? Slug { get; set; }

[JsonPropertyName("userId")]
public string? UserId { get; set; }

Expand Down
5 changes: 3 additions & 2 deletions Notesnook.API/Models/MonographMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ You should have received a copy of the Affero GNU General Public License
*/

using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Runtime.Serialization;

namespace Notesnook.API.Models
Expand All @@ -37,6 +35,9 @@ public required string ItemId
[JsonPropertyName("title")]
public string? Title { get; set; }

[JsonPropertyName("publishUrl")]
public string? PublishUrl { get; set; }

[JsonPropertyName("selfDestruct")]
public bool SelfDestruct { get; set; }

Expand Down
1 change: 1 addition & 0 deletions Streetwriters.Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class Constants
public static string? SUBSCRIPTIONS_CERT_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_PATH");
public static string? SUBSCRIPTIONS_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_KEY_PATH");
public static string[] NOTESNOOK_CORS_ORIGINS => Environment.GetEnvironmentVariable("NOTESNOOK_CORS")?.Split(",") ?? new string[] { };
public static string MONOGRAPH_PUBLIC_URL => Environment.GetEnvironmentVariable("MONOGRAPH_PUBLIC_URL") ?? "https://monogr.ph";
}
}