From aace3b48b13ceadb0012a6bd01db40e8304281bb Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 16 Nov 2025 20:36:04 +0800 Subject: [PATCH] :sparkles: Sharable thought --- ...20251116123552_SharableThought.Designer.cs | 163 ++++++++++++++++++ .../20251116123552_SharableThought.cs | 29 ++++ .../Migrations/AppDatabaseModelSnapshot.cs | 4 + .../Thought/ThoughtController.cs | 41 ++++- .../Thought/ThoughtService.cs | 11 ++ .../Models/ThinkingSequence.cs | 2 + 6 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 DysonNetwork.Insight/Migrations/20251116123552_SharableThought.Designer.cs create mode 100644 DysonNetwork.Insight/Migrations/20251116123552_SharableThought.cs diff --git a/DysonNetwork.Insight/Migrations/20251116123552_SharableThought.Designer.cs b/DysonNetwork.Insight/Migrations/20251116123552_SharableThought.Designer.cs new file mode 100644 index 0000000..41339a0 --- /dev/null +++ b/DysonNetwork.Insight/Migrations/20251116123552_SharableThought.Designer.cs @@ -0,0 +1,163 @@ +// +using System; +using System.Collections.Generic; +using DysonNetwork.Insight; +using DysonNetwork.Shared.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace DysonNetwork.Insight.Migrations +{ + [DbContext(typeof(AppDatabase))] + [Migration("20251116123552_SharableThought")] + partial class SharableThought + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingSequence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AccountId") + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("is_public"); + + b.Property("PaidToken") + .HasColumnType("bigint") + .HasColumnName("paid_token"); + + b.Property("Topic") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("topic"); + + b.Property("TotalToken") + .HasColumnType("bigint") + .HasColumnName("total_token"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_thinking_sequences"); + + b.ToTable("thinking_sequences", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property>("Files") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("files"); + + b.Property("ModelName") + .HasMaxLength(4096) + .HasColumnType("character varying(4096)") + .HasColumnName("model_name"); + + b.Property>("Parts") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("parts"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("SequenceId") + .HasColumnType("uuid") + .HasColumnName("sequence_id"); + + b.Property("TokenCount") + .HasColumnType("bigint") + .HasColumnName("token_count"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("Id") + .HasName("pk_thinking_thoughts"); + + b.HasIndex("SequenceId") + .HasDatabaseName("ix_thinking_thoughts_sequence_id"); + + b.ToTable("thinking_thoughts", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnUnpaidAccount", b => + { + b.Property("AccountId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("account_id"); + + b.Property("MarkedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("marked_at"); + + b.HasKey("AccountId") + .HasName("pk_unpaid_accounts"); + + b.ToTable("unpaid_accounts", (string)null); + }); + + modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b => + { + b.HasOne("DysonNetwork.Shared.Models.SnThinkingSequence", "Sequence") + .WithMany() + .HasForeignKey("SequenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_thinking_thoughts_thinking_sequences_sequence_id"); + + b.Navigation("Sequence"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DysonNetwork.Insight/Migrations/20251116123552_SharableThought.cs b/DysonNetwork.Insight/Migrations/20251116123552_SharableThought.cs new file mode 100644 index 0000000..06704a7 --- /dev/null +++ b/DysonNetwork.Insight/Migrations/20251116123552_SharableThought.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DysonNetwork.Insight.Migrations +{ + /// + public partial class SharableThought : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "is_public", + table: "thinking_sequences", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "is_public", + table: "thinking_sequences"); + } + } +} diff --git a/DysonNetwork.Insight/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Insight/Migrations/AppDatabaseModelSnapshot.cs index a1a0266..4615c96 100644 --- a/DysonNetwork.Insight/Migrations/AppDatabaseModelSnapshot.cs +++ b/DysonNetwork.Insight/Migrations/AppDatabaseModelSnapshot.cs @@ -44,6 +44,10 @@ namespace DysonNetwork.Insight.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("deleted_at"); + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("is_public"); + b.Property("PaidToken") .HasColumnType("bigint") .HasColumnName("paid_token"); diff --git a/DysonNetwork.Insight/Thought/ThoughtController.cs b/DysonNetwork.Insight/Thought/ThoughtController.cs index ebf857e..18cf1f5 100644 --- a/DysonNetwork.Insight/Thought/ThoughtController.cs +++ b/DysonNetwork.Insight/Thought/ThoughtController.cs @@ -22,11 +22,16 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) [Required] public string UserMessage { get; set; } = null!; public string? ServiceId { get; set; } public Guid? SequenceId { get; set; } - public List? AttachedPosts { get; set; } + public List? AttachedPosts { get; set; } = []; public List>? AttachedMessages { get; set; } public List AcceptProposals { get; set; } = []; } + public class UpdateSharingRequest + { + public bool IsPublic { get; set; } + } + public class ThoughtServiceInfo { public string ServiceId { get; set; } = null!; @@ -76,7 +81,6 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) return BadRequest("Service not found or configured."); } - // TODO: Check perk level from `currentUser` if (serviceInfo.PerkLevel > 0 && !currentUser.IsSuperuser) if (currentUser.PerkSubscription is null || PerkSubscriptionPrivilege.GetPrivilegeFromIdentifier(currentUser.PerkSubscription.Identifier) < @@ -407,6 +411,25 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) return Ok(sequences); } + [HttpPatch("sequences/{sequenceId:guid}/sharing")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task UpdateSequenceSharing(Guid sequenceId, [FromBody] UpdateSharingRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + var sequence = await service.GetSequenceAsync(sequenceId); + if (sequence == null) return NotFound(); + if (sequence.AccountId != accountId) return Forbid(); + + sequence.IsPublic = request.IsPublic; + await service.UpdateSequenceAsync(sequence); + + return NoContent(); + } + /// /// Retrieves the thoughts in a specific thinking sequence. /// @@ -419,12 +442,18 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service) [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task>> GetSequenceThoughts(Guid sequenceId) { - if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - var accountId = Guid.Parse(currentUser.Id); - - var sequence = await service.GetOrCreateSequenceAsync(accountId, sequenceId); + var sequence = await service.GetSequenceAsync(sequenceId); if (sequence == null) return NotFound(); + if (!sequence.IsPublic) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); + var accountId = Guid.Parse(currentUser.Id); + + if (sequence.AccountId != accountId) + return StatusCode(403); + } + var thoughts = await service.GetPreviousThoughtsAsync(sequence); return Ok(thoughts); diff --git a/DysonNetwork.Insight/Thought/ThoughtService.cs b/DysonNetwork.Insight/Thought/ThoughtService.cs index 19bc45e..57ef3eb 100644 --- a/DysonNetwork.Insight/Thought/ThoughtService.cs +++ b/DysonNetwork.Insight/Thought/ThoughtService.cs @@ -36,6 +36,17 @@ public class ThoughtService( } } + public async Task GetSequenceAsync(Guid sequenceId) + { + return await db.ThinkingSequences.FindAsync(sequenceId); + } + + public async Task UpdateSequenceAsync(SnThinkingSequence sequence) + { + db.ThinkingSequences.Update(sequence); + await db.SaveChangesAsync(); + } + public async Task SaveThoughtAsync( SnThinkingSequence sequence, List parts, diff --git a/DysonNetwork.Shared/Models/ThinkingSequence.cs b/DysonNetwork.Shared/Models/ThinkingSequence.cs index 85ec9e0..6de24bf 100644 --- a/DysonNetwork.Shared/Models/ThinkingSequence.cs +++ b/DysonNetwork.Shared/Models/ThinkingSequence.cs @@ -12,6 +12,8 @@ public class SnThinkingSequence : ModelBase public long TotalToken { get; set; } public long PaidToken { get; set; } + public bool IsPublic { get; set; } = false; + public Guid AccountId { get; set; } }