Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
65 changes: 56 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 @@ -181,7 +197,8 @@ public async Task<IActionResult> UpdateAsync([FromQuery] string? deviceId, [From
return Ok(new
{
id = monograph.ItemId,
datePublished = monograph.DatePublished
datePublished = monograph.DatePublished,
publishUrl = existingMonograph.ConstructPublishUrl()
});
}
catch (Exception e)
Expand All @@ -208,11 +225,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."
});
}
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

Copy link
Contributor

Choose a reason for hiding this comment

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

What if we use a different endpoint for monographs with slug? It'll prevent a lot of bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The public get monograph API is only called on the monograph frontend. I'm trying to understand how having a separate API to fetch by id and slug is beneficial

Copy link
Contributor

Choose a reason for hiding this comment

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

True but if we use separate URL on the frontend for slugs, we can easily route users based on that. It will bring clarity and prevent doing extra work every time a slug is not matched. It still won't fix this though:

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

Unless we allow both to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

created a separate endpoint for getting monographs by slug

also, in the get monography by Id endpoint, I'm not checking for slug anymore

}

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

[HttpGet("{id}/publish-url")]
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 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}";
}
}
}
11 changes: 7 additions & 4 deletions Notesnook.API/Hubs/SyncV2Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ You should have received a copy of the Affero GNU General Public License
using Microsoft.Extensions.Logging;
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,17 +276,19 @@ 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,
ItemId = m.ItemId ?? m.Id.ToString()
}).ToListAsync();
ItemId = m.ItemId ?? m.Id.ToString(),
PublishUrl = m.ConstructPublishUrl()
}).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)))
throw new HubException("Client rejected monographs.");
}

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 @@ -19,8 +19,6 @@ You should have received a copy of the Affero GNU General Public License

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

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 => ReadSecret("SUBSCRIPTIONS_CERT_PATH");
public static string? SUBSCRIPTIONS_CERT_KEY_PATH => ReadSecret("SUBSCRIPTIONS_CERT_KEY_PATH");
public static string[] NOTESNOOK_CORS_ORIGINS => ReadSecret("NOTESNOOK_CORS")?.Split(",") ?? [];
public static string MONOGRAPH_PUBLIC_URL => ReadSecret("MONOGRAPH_PUBLIC_URL") ?? "https://monogr.phf";

public static string? ReadSecret(string name)
{
Expand Down