Adds SoftDelete/Trash across characters, chapters, locations, beats with a purge schedule and Trash page. Reworks the web client for keyboard-driven navigation (focus helpers, help overlay, keyboard.md doc). Moves the ChapterPage tag editor to the bottom of the page to match CharacterDetailPage.
112 lines
3.9 KiB
C#
112 lines
3.9 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
using Novelly.Api.Beats;
|
|
using Novelly.Api.Chapters;
|
|
using Novelly.Api.Common;
|
|
using Novelly.Api.Novels;
|
|
using Novelly.Api.Tags;
|
|
|
|
namespace Novelly.Api.Characters;
|
|
|
|
public class Character : ISoftDeletable
|
|
{
|
|
public Guid Id { get; set; } = Guid.NewGuid();
|
|
public Guid NovelId { get; set; }
|
|
public Novel? Novel { get; set; }
|
|
|
|
public string Name { get; set; } = string.Empty;
|
|
public CharacterRole Role { get; set; } = CharacterRole.Supporting;
|
|
|
|
public CharacterImportance Importance { get; set; } = CharacterImportance.Supporting;
|
|
|
|
public string? Age { get; set; }
|
|
public string? Pronouns { get; set; }
|
|
public string? Occupation { get; set; }
|
|
|
|
public string? Appearance { get; set; }
|
|
public string? Personality { get; set; }
|
|
public string? Backstory { get; set; }
|
|
|
|
public string? Motivation { get; set; }
|
|
|
|
public string? Conflict { get; set; }
|
|
|
|
public string? Voice { get; set; }
|
|
|
|
public string? Notes { get; set; }
|
|
|
|
public List<string> Aliases { get; set; } = [];
|
|
|
|
public Guid? SameCharacterAsId { get; set; }
|
|
public Character? SameCharacterAs { get; set; }
|
|
public List<Character> OtherIdentities { get; set; } = [];
|
|
|
|
public Guid? RevealedInChapterId { get; set; }
|
|
public Chapter? RevealedInChapter { get; set; }
|
|
public string? IdentityNote { get; set; }
|
|
|
|
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
|
public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;
|
|
public DateTimeOffset? DeletedAt { get; set; }
|
|
|
|
public List<CharacterRelationship> Relationships { get; set; } = [];
|
|
public List<Tag> Tags { get; set; } = [];
|
|
|
|
public List<CharacterArcStage> ArcStages { get; set; } = [];
|
|
|
|
public List<Beat> Beats { get; set; } = [];
|
|
}
|
|
|
|
public class CharacterRelationship
|
|
{
|
|
public Guid Id { get; set; } = Guid.NewGuid();
|
|
|
|
public Guid CharacterId { get; set; }
|
|
public Character? Character { get; set; }
|
|
|
|
public Guid RelatedCharacterId { get; set; }
|
|
public Character? RelatedCharacter { get; set; }
|
|
|
|
public string RelationshipType { get; set; } = string.Empty;
|
|
|
|
public string? Description { get; set; }
|
|
}
|
|
|
|
public class CharacterEntityTypeConfiguration : IEntityTypeConfiguration<Character>
|
|
{
|
|
public void Configure(EntityTypeBuilder<Character> entity)
|
|
{
|
|
entity.Property(c => c.Name).IsRequired().HasMaxLength(200);
|
|
entity.Property(c => c.Role).HasConversion<string>().HasMaxLength(32);
|
|
entity.Property(c => c.Importance).HasConversion<string>().HasMaxLength(32);
|
|
entity.HasIndex(c => c.NovelId);
|
|
entity.HasIndex(c => c.SameCharacterAsId);
|
|
entity.HasQueryFilter(c => c.DeletedAt == null);
|
|
|
|
entity.HasMany(c => c.Relationships).WithOne(r => r.Character!)
|
|
.HasForeignKey(r => r.CharacterId).OnDelete(DeleteBehavior.Cascade);
|
|
|
|
entity.HasMany(c => c.ArcStages).WithOne(s => s.Character!)
|
|
.HasForeignKey(s => s.CharacterId).OnDelete(DeleteBehavior.Cascade);
|
|
|
|
entity.HasOne(c => c.SameCharacterAs).WithMany(c => c.OtherIdentities)
|
|
.HasForeignKey(c => c.SameCharacterAsId).OnDelete(DeleteBehavior.SetNull);
|
|
|
|
entity.HasOne(c => c.RevealedInChapter).WithMany()
|
|
.HasForeignKey(c => c.RevealedInChapterId).OnDelete(DeleteBehavior.SetNull);
|
|
}
|
|
}
|
|
|
|
public class CharacterRelationshipEntityTypeConfiguration : IEntityTypeConfiguration<CharacterRelationship>
|
|
{
|
|
public void Configure(EntityTypeBuilder<CharacterRelationship> entity)
|
|
{
|
|
entity.Property(r => r.RelationshipType).IsRequired().HasMaxLength(120);
|
|
|
|
entity.HasOne(r => r.RelatedCharacter).WithMany()
|
|
.HasForeignKey(r => r.RelatedCharacterId).OnDelete(DeleteBehavior.Restrict);
|
|
|
|
entity.HasQueryFilter(r => r.Character!.DeletedAt == null && r.RelatedCharacter!.DeletedAt == null);
|
|
}
|
|
}
|