feat: normalize subscription architecture, integrate pgvector, and implement Stripe webhook subscription management.

This commit is contained in:
2026-05-05 15:07:48 +02:00
parent e21c24b66d
commit 311eaa8b04
29 changed files with 1699 additions and 199 deletions
@@ -0,0 +1,9 @@
namespace NexusReader.Application.DTOs.User;
public record SubscriptionPlanDto
{
public int Id { get; init; }
public string Name { get; init; } = string.Empty;
public int AITokenLimit { get; init; }
public decimal MonthlyPrice { get; init; }
}
@@ -0,0 +1,25 @@
namespace NexusReader.Application.DTOs.User;
public record UserProfileDto
{
public string Email { get; init; } = string.Empty;
public int AITokensUsed { get; init; }
/// <summary>
/// Relational data for the current subscription plan.
/// </summary>
public SubscriptionPlanDto Plan { get; init; } = new();
public int AverageQuizScore { get; init; }
/// <summary>
/// Summary of the last read book.
/// </summary>
public LastReadBookDto? LastReadBook { get; init; }
}
public record LastReadBookDto
{
public Guid Id { get; init; }
public string Title { get; init; } = string.Empty;
}
@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.AI;
using NexusReader.Application.DTOs.AI;
using NexusReader.Application.Abstractions.Persistence;
using Pgvector;
using Pgvector.EntityFrameworkCore;
using System.Text.Json;
@@ -37,7 +38,7 @@ public class SearchLibrarySemanticallyQueryHandler : IRequestHandler<SearchLibra
{
// 1. Generate embedding for user query
var embeddingResponse = await _embeddingGenerator.GenerateAsync(new[] { request.QueryText }, cancellationToken: cancellationToken);
var queryVector = embeddingResponse.First().Vector.ToArray();
var queryVector = new Vector(embeddingResponse.First().Vector.ToArray());
// 2. Perform Cosine Similarity Search on Knowledge Units
var candidates = await _dbContext.KnowledgeUnits
@@ -31,7 +31,7 @@ public class ProUserHandler : AuthorizationHandler<ProUserRequirement>
}
// Rule 1: Explicit Pro plan
if (user.CurrentPlan == "Pro")
if (user.SubscriptionPlanId == SubscriptionPlan.ProId)
{
context.Succeed(requirement);
return;