monograph: {id}/stats -> {id}/analytics

This commit is contained in:
Abdullah Atta
2025-11-08 12:42:10 +05:00
parent 23b0b2ddfc
commit 5ca9c142e3
6 changed files with 74 additions and 45 deletions

View File

@@ -35,6 +35,7 @@ using Notesnook.API.Authorization;
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;
@@ -272,18 +273,20 @@ namespace Notesnook.API.Controllers
return Content(SVG_PIXEL, "image/svg+xml");
}
[HttpGet("{id}/stats")]
public async Task<IActionResult> GetMonographStatsAsync([FromRoute] string id)
[HttpGet("{id}/analytics")]
public async Task<IActionResult> GetMonographAnalyticsAsync([FromRoute] string id)
{
if (!FeatureAuthorizationHelper.IsFeatureAllowed(Features.MONOGRAPH_ANALYTICS, Clients.Notesnook.Id, User))
return BadRequest(new { error = "Monograph analytics are only available on the Pro & Believer plans." });
var userId = this.User.GetUserId();
var monograph = await FindMonographAsync(id);
if (monograph == null || monograph.Deleted || monograph.UserId != userId)
{
return NotFound();
}
return Ok(new { viewCount = monograph.ViewCount });
return Ok(new { totalViews = monograph.ViewCount });
}
[HttpDelete("{id}")]

View File

@@ -0,0 +1,60 @@
using System.IO;
using System.Security.Claims;
using System.Threading.Tasks;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
using WebMarkupMin.Core;
using WebMarkupMin.Core.Loggers;
namespace Streetwriters.Common.Helpers
{
public enum Features
{
SMS_2FA,
MONOGRAPH_ANALYTICS
}
public static class FeatureAuthorizationHelper
{
private static SubscriptionPlan? GetUserSubscriptionPlan(string clientId, ClaimsPrincipal user)
{
var claimKey = $"{clientId}:status";
var status = user.FindFirstValue(claimKey);
switch (status)
{
case "free":
return SubscriptionPlan.FREE;
case "believer":
return SubscriptionPlan.BELIEVER;
case "education":
return SubscriptionPlan.EDUCATION;
case "essential":
return SubscriptionPlan.ESSENTIAL;
case "pro":
return SubscriptionPlan.PRO;
default:
return null;
}
}
public static bool IsFeatureAllowed(Features feature, string clientId, ClaimsPrincipal user)
{
if (Constants.IS_SELF_HOSTED)
return true;
var status = GetUserSubscriptionPlan(clientId, user);
switch (feature)
{
case Features.SMS_2FA:
case Features.MONOGRAPH_ANALYTICS:
return status == SubscriptionPlan.LEGACY_PRO ||
status == SubscriptionPlan.PRO ||
status == SubscriptionPlan.EDUCATION ||
status == SubscriptionPlan.BELIEVER;
default:
return false;
}
}
}
}

View File

@@ -29,6 +29,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Helpers;
using Streetwriters.Common.Models;
using Streetwriters.Identity.Interfaces;
using Streetwriters.Identity.Models;
@@ -53,6 +54,9 @@ namespace Streetwriters.Identity.Controllers
var user = await UserManager.GetUserAsync(User) ?? throw new Exception("User not found.");
if (form.Type == MFAMethods.SMS && !FeatureAuthorizationHelper.IsFeatureAllowed(Features.SMS_2FA, client.Id, User))
throw new Exception("2FA via SMS is only available on Pro & Believer plans.");
try
{
switch (form.Type)
@@ -62,7 +66,7 @@ namespace Streetwriters.Identity.Controllers
return Ok(authenticatorDetails);
case "sms":
case "email":
await MFAService.SendOTPAsync(user, client, form, true);
await MFAService.SendOTPAsync(user, client, form);
return Ok();
default:
return BadRequest("Invalid authenticator type.");

View File

@@ -36,7 +36,7 @@ namespace Streetwriters.Identity.Interfaces
bool IsValidMFAMethod(string method);
bool IsValidMFAMethod(string method, User user);
Task<AuthenticatorDetails> GetAuthenticatorDetailsAsync(User user, IClient client);
Task SendOTPAsync(User user, IClient client, MultiFactorSetupForm form, bool isSetup = false);
Task SendOTPAsync(User user, IClient client, MultiFactorSetupForm form);
Task<bool> VerifyOTPAsync(User user, string code, string method);
}
}

View File

@@ -25,7 +25,6 @@ using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Models;
@@ -168,18 +167,12 @@ namespace Streetwriters.Identity.Services
};
}
public async Task SendOTPAsync(User user, IClient client, MultiFactorSetupForm form, bool isSetup = false)
public async Task SendOTPAsync(User user, IClient client, MultiFactorSetupForm form)
{
var method = form.Type;
if ((method != MFAMethods.Email && method != MFAMethods.SMS) || !IsValidMFAMethod(method))
throw new Exception("Invalid method.");
if (isSetup &&
method == MFAMethods.SMS &&
!UserService.IsSMSMFAAllowed(client.Id, user))
throw new Exception("Due to the high costs of SMS, 2FA via SMS is only available on Pro & Believer plans.");
// if (!user.EmailConfirmed) throw new Exception("Please confirm your email before activating 2FA by email.");
await GetAuthenticatorDetailsAsync(user, client);
switch (method)

View File

@@ -29,37 +29,6 @@ namespace Streetwriters.Identity.Services
{
public class UserService
{
private static SubscriptionPlan? GetUserSubscriptionPlan(string clientId, User user)
{
var claimKey = GetClaimKey(clientId);
var status = user.Claims.FirstOrDefault((c) => c.ClaimType == claimKey)?.ClaimValue;
switch (status)
{
case "free":
return SubscriptionPlan.FREE;
case "believer":
return SubscriptionPlan.BELIEVER;
case "education":
return SubscriptionPlan.EDUCATION;
case "essential":
return SubscriptionPlan.ESSENTIAL;
case "pro":
return SubscriptionPlan.PRO;
default:
return null;
}
}
public static bool IsSMSMFAAllowed(string clientId, User user)
{
var status = GetUserSubscriptionPlan(clientId, user);
if (status == null) return false;
return status == SubscriptionPlan.LEGACY_PRO ||
status == SubscriptionPlan.PRO ||
status == SubscriptionPlan.EDUCATION ||
status == SubscriptionPlan.BELIEVER;
}
public static Claim SubscriptionPlanToClaim(string clientId, Subscription subscription)
{
var claimKey = GetClaimKey(clientId);