fix(arch): revert WASM proxies, restore CQRS integrity, and add EF migration for Ebook state
This commit is contained in:
@@ -193,6 +193,9 @@ namespace NexusReader.Data.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsReadyForReading")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b.Property<string>("LastChapter")
|
b.Property<string>("LastChapter")
|
||||||
.HasMaxLength(255)
|
.HasMaxLength(255)
|
||||||
.HasColumnType("character varying(255)");
|
.HasColumnType("character varying(255)");
|
||||||
|
|||||||
+703
@@ -0,0 +1,703 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using NexusReader.Data.Persistence;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using Pgvector;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NexusReader.Data.Persistence.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260513181743_AddEbookReadyFlag")]
|
||||||
|
partial class AddEbookReadyFlag
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "vector");
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("RoleNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoleClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderKey")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderDisplayName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("LoginProvider", "ProviderKey");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserLogins", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "LoginProvider", "Name");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Authors");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.Ebook", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AddedDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("AuthorId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("CoverUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("FilePath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsReadyForReading")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("LastChapter")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<int>("LastChapterIndex")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastReadDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<double>("Progress")
|
||||||
|
.HasColumnType("double precision");
|
||||||
|
|
||||||
|
b.Property<string>("TenantId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AuthorId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Ebooks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.KnowledgeUnit", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("MetadataJson")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("SourceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("TenantId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<Vector>("Vector")
|
||||||
|
.HasColumnType("vector(768)");
|
||||||
|
|
||||||
|
b.Property<string>("Version")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SourceId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.ToTable("KnowledgeUnits");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.KnowledgeUnitLink", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("RelationType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.Property<string>("SourceUnitId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("TargetUnitId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SourceUnitId");
|
||||||
|
|
||||||
|
b.HasIndex("TargetUnitId");
|
||||||
|
|
||||||
|
b.ToTable("KnowledgeUnitLinks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.NexusUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("AITokenLimit")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("AITokensUsed")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastAiActionDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastReadAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("LastReadPageId")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("character varying(255)");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("SubscriptionPlanId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(1);
|
||||||
|
|
||||||
|
b.Property<string>("TenantId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasDatabaseName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UserNameIndex");
|
||||||
|
|
||||||
|
b.HasIndex("SubscriptionPlanId");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.QuizResult", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CompletedDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("Score")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("TenantId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Topic")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("TotalQuestions")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("QuizResults");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.SemanticKnowledgeCache", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("ContentHash")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("JsonData")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ModelId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.Property<string>("OriginalText")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PromptVersion")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)");
|
||||||
|
|
||||||
|
b.Property<string>("TenantId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<Vector>("Vector")
|
||||||
|
.HasColumnType("vector(1536)");
|
||||||
|
|
||||||
|
b.HasKey("ContentHash");
|
||||||
|
|
||||||
|
b.HasIndex("ContentHash")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("TenantId");
|
||||||
|
|
||||||
|
b.ToTable("SemanticKnowledgeCache");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.SubscriptionPlan", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("AITokenLimit")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<bool>("IsUnlimitedTokens")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<decimal>("MonthlyPrice")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<string>("PlanName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.Property<string>("StripeProductId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PlanName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("SubscriptionPlans");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
AITokenLimit = 5000,
|
||||||
|
IsUnlimitedTokens = false,
|
||||||
|
MonthlyPrice = 0m,
|
||||||
|
PlanName = "Free",
|
||||||
|
StripeProductId = "prod_Free789"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
AITokenLimit = 10000,
|
||||||
|
IsUnlimitedTokens = false,
|
||||||
|
MonthlyPrice = 9.99m,
|
||||||
|
PlanName = "Basic",
|
||||||
|
StripeProductId = "prod_basic_placeholder"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
AITokenLimit = 50000,
|
||||||
|
IsUnlimitedTokens = false,
|
||||||
|
MonthlyPrice = 19.99m,
|
||||||
|
PlanName = "Pro",
|
||||||
|
StripeProductId = "prod_pro_placeholder"
|
||||||
|
},
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
AITokenLimit = 1000000000,
|
||||||
|
IsUnlimitedTokens = true,
|
||||||
|
MonthlyPrice = 99.99m,
|
||||||
|
PlanName = "Enterprise",
|
||||||
|
StripeProductId = "prod_enterprise_placeholder"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.NexusUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.NexusUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.NexusUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.NexusUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.Ebook", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.Author", "Author")
|
||||||
|
.WithMany("Ebooks")
|
||||||
|
.HasForeignKey("AuthorId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.NexusUser", "User")
|
||||||
|
.WithMany("Ebooks")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Author");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.KnowledgeUnitLink", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.KnowledgeUnit", "SourceUnit")
|
||||||
|
.WithMany("OutgoingLinks")
|
||||||
|
.HasForeignKey("SourceUnitId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.KnowledgeUnit", "TargetUnit")
|
||||||
|
.WithMany("IncomingLinks")
|
||||||
|
.HasForeignKey("TargetUnitId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("SourceUnit");
|
||||||
|
|
||||||
|
b.Navigation("TargetUnit");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.NexusUser", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.SubscriptionPlan", "SubscriptionPlan")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SubscriptionPlanId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("SubscriptionPlan");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.QuizResult", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NexusReader.Domain.Entities.NexusUser", "User")
|
||||||
|
.WithMany("QuizResults")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.Author", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Ebooks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.KnowledgeUnit", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("IncomingLinks");
|
||||||
|
|
||||||
|
b.Navigation("OutgoingLinks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NexusReader.Domain.Entities.NexusUser", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Ebooks");
|
||||||
|
|
||||||
|
b.Navigation("QuizResults");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace NexusReader.Data.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddEbookReadyFlag : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsReadyForReading",
|
||||||
|
table: "Ebooks",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsReadyForReading",
|
||||||
|
table: "Ebooks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,12 +45,12 @@ builder.Services.AddHttpClient("NexusAPI", client =>
|
|||||||
|
|
||||||
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("NexusAPI"));
|
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("NexusAPI"));
|
||||||
|
|
||||||
// Real WASM implementations for application abstractions
|
// Dummy registrations for server-only handlers to satisfy DI validation in WASM
|
||||||
builder.Services.AddSingleton<IDbContextFactory<AppDbContext>>(new ThrowingDbContextFactory());
|
builder.Services.AddSingleton<IDbContextFactory<AppDbContext>>(new ThrowingDbContextFactory());
|
||||||
builder.Services.AddScoped<IEmbeddingGenerator<string, Embedding<float>>, WasmEmbeddingGenerator>();
|
builder.Services.AddSingleton<IEmbeddingGenerator<string, Embedding<float>>>(new ThrowingEmbeddingGenerator());
|
||||||
builder.Services.AddScoped<IBookStorageService, WasmBookStorageService>();
|
builder.Services.AddSingleton<IBookStorageService>(new ThrowingBookStorageService());
|
||||||
builder.Services.AddScoped<IEbookRepository, WasmEbookRepository>();
|
builder.Services.AddSingleton<IEbookRepository>(new ThrowingEbookRepository());
|
||||||
builder.Services.AddScoped<ISyncBroadcaster, WasmSyncBroadcaster>();
|
builder.Services.AddSingleton<ISyncBroadcaster>(new ThrowingSyncBroadcaster());
|
||||||
|
|
||||||
builder.Services.AddApplication();
|
builder.Services.AddApplication();
|
||||||
builder.Services.AddScoped<IEpubReader, WasmEpubReader>();
|
builder.Services.AddScoped<IEpubReader, WasmEpubReader>();
|
||||||
@@ -60,5 +60,42 @@ await builder.Build().RunAsync();
|
|||||||
|
|
||||||
public class ThrowingDbContextFactory : IDbContextFactory<AppDbContext>
|
public class ThrowingDbContextFactory : IDbContextFactory<AppDbContext>
|
||||||
{
|
{
|
||||||
public AppDbContext CreateDbContext() => throw new NotSupportedException("DbContext cannot be used in WASM client. Use API proxies for data access.");
|
public AppDbContext CreateDbContext() => throw new NotSupportedException("DbContext cannot be used in WASM client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ThrowingEmbeddingGenerator : IEmbeddingGenerator<string, Embedding<float>>
|
||||||
|
{
|
||||||
|
public void Dispose() { }
|
||||||
|
public Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(IEnumerable<string> values, EmbeddingGenerationOptions? options = null, CancellationToken cancellationToken = default)
|
||||||
|
=> throw new NotSupportedException("Embedding generation cannot be used in WASM client.");
|
||||||
|
public object? GetService(Type serviceType, object? serviceKey = null) => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ThrowingBookStorageService : IBookStorageService
|
||||||
|
{
|
||||||
|
private const string ErrorMessage = "File storage operations are not supported in the WASM client. Use the API endpoint for ingestion.";
|
||||||
|
|
||||||
|
public Task<string> SaveEbookAsync(byte[] data, string fileName) => throw new NotSupportedException(ErrorMessage);
|
||||||
|
public Task<string> SaveEbookAsync(Stream data, string fileName) => throw new NotSupportedException(ErrorMessage);
|
||||||
|
public Task<string?> SaveCoverAsync(byte[] data, string fileName) => throw new NotSupportedException(ErrorMessage);
|
||||||
|
public Task<string?> SaveCoverAsync(Stream data, string fileName) => throw new NotSupportedException(ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ThrowingEbookRepository : IEbookRepository
|
||||||
|
{
|
||||||
|
private const string ErrorMessage = "Ebook repository operations are not supported in the WASM client. Use the API endpoint for data access.";
|
||||||
|
|
||||||
|
public Task<Author?> FindAuthorByNameAsync(string name, CancellationToken cancellationToken = default) => throw new NotSupportedException(ErrorMessage);
|
||||||
|
public void AddAuthor(Author author) => throw new NotSupportedException(ErrorMessage);
|
||||||
|
public void AddEbook(Ebook ebook) => throw new NotSupportedException(ErrorMessage);
|
||||||
|
public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException(ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ThrowingSyncBroadcaster : ISyncBroadcaster
|
||||||
|
{
|
||||||
|
public Task BroadcastProgressAsync(string userId, string pageId, DateTime timestamp, string? excludedConnectionId, CancellationToken cancellationToken = default)
|
||||||
|
=> throw new NotSupportedException("Real-time broadcasting can only be performed by the server.");
|
||||||
|
|
||||||
|
public Task BroadcastIngestionProgressAsync(string userId, string message, double progress, CancellationToken cancellationToken = default)
|
||||||
|
=> throw new NotSupportedException("Real-time broadcasting can only be performed by the server.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
using System.Net.Http.Json;
|
|
||||||
using NexusReader.Application.Abstractions.Services;
|
|
||||||
|
|
||||||
namespace NexusReader.Web.Client.Services;
|
|
||||||
|
|
||||||
public class WasmBookStorageService : IBookStorageService
|
|
||||||
{
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public WasmBookStorageService(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
_httpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> SaveEbookAsync(byte[] data, string fileName)
|
|
||||||
{
|
|
||||||
var response = await _httpClient.PostAsJsonAsync("/api/storage/save/ebook", new { data, fileName });
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var result = await response.Content.ReadFromJsonAsync<StorageResponse>();
|
|
||||||
return result?.Path ?? string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> SaveEbookAsync(Stream data, string fileName)
|
|
||||||
{
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
await data.CopyToAsync(ms);
|
|
||||||
return await SaveEbookAsync(ms.ToArray(), fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> SaveCoverAsync(byte[] data, string fileName)
|
|
||||||
{
|
|
||||||
if (data == null || data.Length == 0) return null;
|
|
||||||
var response = await _httpClient.PostAsJsonAsync("/api/storage/save/cover", new { data, fileName });
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var result = await response.Content.ReadFromJsonAsync<StorageResponse>();
|
|
||||||
return result?.Path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> SaveCoverAsync(Stream data, string fileName)
|
|
||||||
{
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
await data.CopyToAsync(ms);
|
|
||||||
return await SaveCoverAsync(ms.ToArray(), fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private record StorageResponse(string Path);
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using System.Net.Http.Json;
|
|
||||||
using NexusReader.Application.Abstractions.Persistence;
|
|
||||||
using NexusReader.Domain.Entities;
|
|
||||||
|
|
||||||
namespace NexusReader.Web.Client.Services;
|
|
||||||
|
|
||||||
public class WasmEbookRepository : IEbookRepository
|
|
||||||
{
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public WasmEbookRepository(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
_httpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Author?> FindAuthorByNameAsync(string name, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var response = await _httpClient.PostAsJsonAsync("/api/repository/author/find", new { name }, cancellationToken);
|
|
||||||
if (response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
return await response.Content.ReadFromJsonAsync<Author>(cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddAuthor(Author author)
|
|
||||||
{
|
|
||||||
// For a repository in WASM, we can't easily do 'void' fire-and-forget Add without a local state.
|
|
||||||
// However, we can either queue it or just do nothing if the caller expects SaveChangesAsync to handle it.
|
|
||||||
// But the common pattern for this app seems to be calling the API.
|
|
||||||
// For now, we'll assume the entity will be sent during SaveChanges or a separate command.
|
|
||||||
// Given the constraints, we'll mark it for later serialization or just throw if not supported.
|
|
||||||
// Better yet: we'll implement a 'Real' enough version that tracks changes locally.
|
|
||||||
_stagedAuthors.Add(author);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddEbook(Ebook ebook)
|
|
||||||
{
|
|
||||||
_stagedEbooks.Add(ebook);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly List<Author> _stagedAuthors = new();
|
|
||||||
private readonly List<Ebook> _stagedEbooks = new();
|
|
||||||
|
|
||||||
public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
int count = 0;
|
|
||||||
foreach (var author in _stagedAuthors)
|
|
||||||
{
|
|
||||||
await _httpClient.PostAsJsonAsync("/api/repository/author/add", author, cancellationToken);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
foreach (var ebook in _stagedEbooks)
|
|
||||||
{
|
|
||||||
await _httpClient.PostAsJsonAsync("/api/repository/ebook/add", ebook, cancellationToken);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
_stagedAuthors.Clear();
|
|
||||||
_stagedEbooks.Clear();
|
|
||||||
|
|
||||||
await _httpClient.PostAsync("/api/repository/save", null, cancellationToken);
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Net.Http.Json;
|
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
|
|
||||||
namespace NexusReader.Web.Client.Services;
|
|
||||||
|
|
||||||
public class WasmEmbeddingGenerator : IEmbeddingGenerator<string, Embedding<float>>
|
|
||||||
{
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public WasmEmbeddingGenerator(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
_httpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() { }
|
|
||||||
|
|
||||||
public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
|
|
||||||
IEnumerable<string> values,
|
|
||||||
EmbeddingGenerationOptions? options = null,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var response = await _httpClient.PostAsJsonAsync("/api/ai/embeddings", new { values, options }, cancellationToken);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var result = await response.Content.ReadFromJsonAsync<GeneratedEmbeddings<Embedding<float>>>(cancellationToken: cancellationToken);
|
|
||||||
return result ?? new GeneratedEmbeddings<Embedding<float>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
||||||
{
|
|
||||||
if (serviceType == typeof(IEmbeddingGenerator<string, Embedding<float>>)) return this;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using System.Net.Http.Json;
|
|
||||||
using NexusReader.Application.Abstractions.Messaging;
|
|
||||||
|
|
||||||
namespace NexusReader.Web.Client.Services;
|
|
||||||
|
|
||||||
public class WasmSyncBroadcaster : ISyncBroadcaster
|
|
||||||
{
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
|
|
||||||
public WasmSyncBroadcaster(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
_httpClient = httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task BroadcastProgressAsync(
|
|
||||||
string userId,
|
|
||||||
string pageId,
|
|
||||||
DateTime timestamp,
|
|
||||||
string? excludedConnectionId,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await _httpClient.PostAsJsonAsync("/api/broadcaster/progress", new
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
pageId,
|
|
||||||
timestamp,
|
|
||||||
excludedConnectionId
|
|
||||||
}, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task BroadcastIngestionProgressAsync(
|
|
||||||
string userId,
|
|
||||||
string message,
|
|
||||||
double progress,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
await _httpClient.PostAsJsonAsync("/api/broadcaster/ingestion-progress", new
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
message,
|
|
||||||
progress
|
|
||||||
}, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -253,13 +253,6 @@ app.MapGet("/api/epub/{ebookId:guid}/{index:int}", async (Guid ebookId, int inde
|
|||||||
return Results.BadRequest(errorMsg);
|
return Results.BadRequest(errorMsg);
|
||||||
}).RequireAuthorization();
|
}).RequireAuthorization();
|
||||||
|
|
||||||
// Proxy API for AI services (Embeddings)
|
|
||||||
app.MapPost("/api/ai/embeddings", async (EmbeddingsRequest request, IEmbeddingGenerator<string, Embedding<float>> generator) =>
|
|
||||||
{
|
|
||||||
var result = await generator.GenerateAsync(request.Values, request.Options);
|
|
||||||
return Results.Ok(result);
|
|
||||||
}).RequireAuthorization();
|
|
||||||
|
|
||||||
var knowledgeApi = app.MapGroup("/api/knowledge").RequireAuthorization("HasAvailableTokens");
|
var knowledgeApi = app.MapGroup("/api/knowledge").RequireAuthorization("HasAvailableTokens");
|
||||||
|
|
||||||
knowledgeApi.MapPost("/", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
|
knowledgeApi.MapPost("/", async (KnowledgeRequest request, ClaimsPrincipal user, IKnowledgeService knowledgeService) =>
|
||||||
@@ -303,63 +296,6 @@ knowledgeApi.MapDelete("/", async (IKnowledgeService knowledgeService) =>
|
|||||||
return Results.BadRequest(errorMsg);
|
return Results.BadRequest(errorMsg);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Proxy API for WASM Repository calls
|
|
||||||
var repoApi = app.MapGroup("/api/repository").RequireAuthorization();
|
|
||||||
|
|
||||||
repoApi.MapPost("/author/find", async (AuthorFindRequest request, IEbookRepository repo) =>
|
|
||||||
{
|
|
||||||
var author = await repo.FindAuthorByNameAsync(request.Name);
|
|
||||||
return author != null ? Results.Ok(author) : Results.NotFound();
|
|
||||||
});
|
|
||||||
|
|
||||||
repoApi.MapPost("/author/add", (Author author, IEbookRepository repo) =>
|
|
||||||
{
|
|
||||||
repo.AddAuthor(author);
|
|
||||||
return Results.Ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
repoApi.MapPost("/ebook/add", (Ebook ebook, IEbookRepository repo) =>
|
|
||||||
{
|
|
||||||
repo.AddEbook(ebook);
|
|
||||||
return Results.Ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
repoApi.MapPost("/save", async (IEbookRepository repo) =>
|
|
||||||
{
|
|
||||||
await repo.SaveChangesAsync();
|
|
||||||
return Results.Ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Proxy API for WASM Broadcaster calls
|
|
||||||
var broadcasterApi = app.MapGroup("/api/broadcaster").RequireAuthorization();
|
|
||||||
|
|
||||||
broadcasterApi.MapPost("/progress", async (BroadcastProgressRequest request, ISyncBroadcaster broadcaster) =>
|
|
||||||
{
|
|
||||||
await broadcaster.BroadcastProgressAsync(request.UserId, request.PageId, request.Timestamp, request.ExcludedConnectionId);
|
|
||||||
return Results.Ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
broadcasterApi.MapPost("/ingestion-progress", async (BroadcastIngestionProgressRequest request, ISyncBroadcaster broadcaster) =>
|
|
||||||
{
|
|
||||||
await broadcaster.BroadcastIngestionProgressAsync(request.UserId, request.Message, request.Progress);
|
|
||||||
return Results.Ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Proxy API for WASM Storage calls
|
|
||||||
var storageApi = app.MapGroup("/api/storage").RequireAuthorization();
|
|
||||||
|
|
||||||
storageApi.MapPost("/save/ebook", async (StorageRequest request, IBookStorageService storage) =>
|
|
||||||
{
|
|
||||||
var path = await storage.SaveEbookAsync(request.Data, request.FileName);
|
|
||||||
return Results.Ok(new { Path = path });
|
|
||||||
});
|
|
||||||
|
|
||||||
storageApi.MapPost("/save/cover", async (StorageRequest request, IBookStorageService storage) =>
|
|
||||||
{
|
|
||||||
var path = await storage.SaveCoverAsync(request.Data, request.FileName);
|
|
||||||
return Results.Ok(new { Path = path });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.MapPost("/api/library/ingest", async ([FromBody] IngestEbookRequest request, ClaimsPrincipal user, IMediator mediator) =>
|
app.MapPost("/api/library/ingest", async ([FromBody] IngestEbookRequest request, ClaimsPrincipal user, IMediator mediator) =>
|
||||||
{
|
{
|
||||||
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
@@ -585,8 +521,3 @@ app.Run();
|
|||||||
|
|
||||||
public record KnowledgeRequest(string Text);
|
public record KnowledgeRequest(string Text);
|
||||||
public record GroundednessRequest(string Answer, string Context);
|
public record GroundednessRequest(string Answer, string Context);
|
||||||
public record AuthorFindRequest(string Name);
|
|
||||||
public record BroadcastProgressRequest(string UserId, string PageId, DateTime Timestamp, string? ExcludedConnectionId);
|
|
||||||
public record BroadcastIngestionProgressRequest(string UserId, string Message, double Progress);
|
|
||||||
public record StorageRequest(byte[] Data, string FileName);
|
|
||||||
public record EmbeddingsRequest(IEnumerable<string> Values, EmbeddingGenerationOptions? Options);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user