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
74 changes: 68 additions & 6 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 Down Expand Up @@ -213,6 +230,28 @@ public async Task<IActionResult> GetUserMonographsAsync()
public async Task<IActionResult> GetMonographAsync([FromRoute] string id)
{
var monograph = await FindMonographAsync(id);

if (monograph == null || monograph.Deleted)
{
return NotFound(new
{
error = "invalid_id",
error_description = $"No such monograph found."
});
}

if (monograph.EncryptedContent == null)
monograph.Content = monograph.CompressedContent?.DecompressBrotli();
monograph.ItemId ??= monograph.Id;
return Ok(monograph);
}

[HttpGet("slug/{slug}")]
[AllowAnonymous]
public async Task<IActionResult> GetMonographBySlugAsync([FromRoute] string slug)
{
var monograph = await FindMonographBySlugAsync(slug);

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

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

var isPro = FeatureAuthorizationHelper.IsFeatureAllowed(Features.MONOGRAPH_ANALYTICS, Clients.Notesnook.Id, User);
var totalViews = isPro ? monograph.ViewCount : 0;

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

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}";
}
}
}
34 changes: 25 additions & 9 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,15 +276,30 @@ 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
{
DatePublished = m.DatePublished,
Deleted = m.Deleted,
Password = m.Password,
SelfDestruct = m.SelfDestruct,
Title = m.Title,
ItemId = m.ItemId ?? m.Id.ToString()
}).ToListAsync();
var userMonographs = Repositories.Monographs.Collection
.Find(filter)
.Project((m) => new Monograph
{
DatePublished = m.DatePublished,
Deleted = m.Deleted,
Password = m.Password,
SelfDestruct = m.SelfDestruct,
Title = m.Title,
ItemId = m.ItemId ?? m.Id.ToString(),
Slug = m.Slug
})
.ToList()
.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(),
PublishUrl = m.ConstructPublishUrl()
})
.ToList();

if (userMonographs.Count > 0 && !await Clients.Caller.SendMonographs(userMonographs).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.ph";

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