diff --git a/deploy/portainer-stack.yml b/deploy/portainer-stack.yml
index 6492e8d..a610223 100644
--- a/deploy/portainer-stack.yml
+++ b/deploy/portainer-stack.yml
@@ -1,6 +1,6 @@
services:
beacon-server:
- image: beacon-server:0.1.2
+ image: beacon-server:0.1.7
restart: always
environment:
ASPNETCORE_ENVIRONMENT: Production
@@ -59,7 +59,7 @@ services:
/bin/sh -c '(while sleep 86400; do nginx -s reload; done) & exec nginx -g "daemon off;"'
beacon-backup:
- image: beacon-backup:0.1.2
+ image: beacon-backup:0.1.7
restart: always
environment:
BACKUP_INTERVAL_SECONDS: "86400"
diff --git a/docs/ROADMAP-profiles.md b/docs/ROADMAP-profiles.md
index e93ff7b..3e87c5d 100644
--- a/docs/ROADMAP-profiles.md
+++ b/docs/ROADMAP-profiles.md
@@ -45,7 +45,7 @@ that makes the addition cheap.
```
ProfileEntity
Id, AccountId, CharacterName, WorldId
- DisplayName, Pronouns, Age, Race, Occupation
+ DisplayName, Age, Race, Occupation
Appearance, Personality, Background (long text)
Hooks (short "what you could approach me about" lines)
PortraitImageId -> reuses BeaconImageEntity's storage
diff --git a/src/Beacon.Plugin/Beacon.Plugin.csproj b/src/Beacon.Plugin/Beacon.Plugin.csproj
index ae4bf92..6806467 100644
--- a/src/Beacon.Plugin/Beacon.Plugin.csproj
+++ b/src/Beacon.Plugin/Beacon.Plugin.csproj
@@ -4,7 +4,7 @@
Beacon
Beacon
- 0.1.6.0
+ 0.1.7.0
enable
diff --git a/src/Beacon.Plugin/UI/ProfileLabels.cs b/src/Beacon.Plugin/UI/ProfileLabels.cs
index 33193bb..d83edd2 100644
--- a/src/Beacon.Plugin/UI/ProfileLabels.cs
+++ b/src/Beacon.Plugin/UI/ProfileLabels.cs
@@ -102,16 +102,6 @@ public static class ProfileLabels
+ "Plenty of people want this and want nothing to do with the explicit tag.",
};
- public static string Describe(AgeRange age) => age switch
- {
- AgeRange.YoungAdult => "Young adult",
- AgeRange.Adult => "Adult",
- AgeRange.MiddleAged => "Middle-aged",
- AgeRange.Elder => "Elder",
- AgeRange.Ageless => "Ageless",
- _ => "Unsaid",
- };
-
public static string Describe(GalleryCategory category) => category switch
{
GalleryCategory.FullBody => "Full body",
diff --git a/src/Beacon.Plugin/Windows/ChronicleWindow.cs b/src/Beacon.Plugin/Windows/ChronicleWindow.cs
index be4bb6c..20beedf 100644
--- a/src/Beacon.Plugin/Windows/ChronicleWindow.cs
+++ b/src/Beacon.Plugin/Windows/ChronicleWindow.cs
@@ -433,11 +433,8 @@ private void DrawCardContents(ProfileDto profile, float scale)
if (!string.IsNullOrWhiteSpace(profile.Identity.Lineage))
details.Add(profile.Identity.Lineage);
- if (profile.Identity.Age != AgeRange.Unspecified)
- details.Add(ProfileLabels.Describe(profile.Identity.Age));
-
- if (!string.IsNullOrWhiteSpace(profile.Identity.Pronouns))
- details.Add(profile.Identity.Pronouns!);
+ if (profile.Identity.Age is { } age)
+ details.Add($"Age {age}");
if (details.Count > 0)
Ornament.Text(Theme.InkSoft, string.Join(" · ", details));
diff --git a/src/Beacon.Plugin/Windows/ProfileEditorWindow.cs b/src/Beacon.Plugin/Windows/ProfileEditorWindow.cs
index e06db1a..e9a33a7 100644
--- a/src/Beacon.Plugin/Windows/ProfileEditorWindow.cs
+++ b/src/Beacon.Plugin/Windows/ProfileEditorWindow.cs
@@ -46,10 +46,9 @@ public sealed class ProfileEditorWindow : Window, IDisposable
private string race = string.Empty;
private string clan = string.Empty;
private string gender = string.Empty;
- private string pronouns = string.Empty;
private string quote = string.Empty;
private readonly string[] archetype = ["", "", ""];
- private AgeRange age = AgeRange.Unspecified;
+ private string age = string.Empty;
private readonly HashSet traits = [];
private readonly HashSet tones = [];
@@ -139,10 +138,9 @@ public void Open(ProfileDto? profile)
race = appearance.Race ?? string.Empty;
clan = appearance.Clan ?? string.Empty;
gender = appearance.Gender ?? string.Empty;
- pronouns = string.Empty;
quote = string.Empty;
Array.Fill(archetype, string.Empty);
- age = AgeRange.Unspecified;
+ age = string.Empty;
length = RpLength.Casual;
boundaries = string.Empty;
@@ -164,9 +162,8 @@ public void Open(ProfileDto? profile)
race = identity.Race ?? string.Empty;
clan = identity.Clan ?? string.Empty;
gender = identity.Gender ?? string.Empty;
- pronouns = identity.Pronouns ?? string.Empty;
quote = identity.Quote ?? string.Empty;
- age = identity.Age;
+ age = identity.Age?.ToString() ?? string.Empty;
for (var i = 0; i < archetype.Length; i++)
archetype[i] = i < identity.Archetype.Count ? identity.Archetype[i] : string.Empty;
@@ -432,24 +429,10 @@ private void DrawCharacter(float scale)
ImGui.SetNextItemWidth(half);
ImGui.InputTextWithHint("##gender", "Gender", ref gender, 32);
- ImGui.SameLine(0, 8f * scale);
- ImGui.SetNextItemWidth(half);
- ImGui.InputTextWithHint("##pronouns", "Pronouns", ref pronouns, 32);
+ Ornament.PageLabel("Age");
ImGui.SetNextItemWidth(half);
- if (ImGui.BeginCombo("##age", ProfileLabels.Describe(age)))
- {
- foreach (var option in Enum.GetValues())
- {
- if (ImGui.Selectable(ProfileLabels.Describe(option), age == option))
- age = option;
- }
-
- ImGui.EndCombo();
- }
-
- ImGui.SameLine();
- Ornament.Text(Theme.MutedDeep, "A bracket, not a number.");
+ ImGui.InputTextWithHint("##age", "Age", ref age, 5, ImGuiInputTextFlags.CharsDecimal);
ImGui.Spacing();
Ornament.PageLabel("A line they might say");
@@ -781,6 +764,20 @@ private async Task SubmitAsync()
var characterName = editing?.CharacterName ?? location.CurrentCharacterName;
var worldId = editing?.WorldId ?? location.CurrentWorldId;
+ int? ageYears = null;
+ if (!string.IsNullOrWhiteSpace(age))
+ {
+ if (!int.TryParse(age, out var parsedAge)
+ || parsedAge < ProfileLimits.AgeMin
+ || parsedAge > ProfileLimits.AgeMax)
+ {
+ validationError = $"Age must be between {ProfileLimits.AgeMin} and {ProfileLimits.AgeMax}.";
+ return;
+ }
+
+ ageYears = parsedAge;
+ }
+
if (string.IsNullOrWhiteSpace(characterName) || worldId == 0)
{
validationError = "Beacon cannot tell which character this is. Wait until you have loaded in.";
@@ -802,9 +799,8 @@ private async Task SubmitAsync()
Title = Blank(title),
Race = Blank(race),
Clan = Blank(clan),
- Age = age,
+ Age = ageYears,
Gender = Blank(gender),
- Pronouns = Blank(pronouns),
Archetype = archetype.Where(a => !string.IsNullOrWhiteSpace(a)).Select(a => a.Trim()).ToList(),
Quote = Blank(quote),
},
diff --git a/src/Beacon.Server/Data/BeaconDbContext.cs b/src/Beacon.Server/Data/BeaconDbContext.cs
index 1876fd2..6fadf12 100644
--- a/src/Beacon.Server/Data/BeaconDbContext.cs
+++ b/src/Beacon.Server/Data/BeaconDbContext.cs
@@ -167,7 +167,6 @@ protected override void OnModelCreating(ModelBuilder b)
e.Property(x => x.Race).HasMaxLength(32);
e.Property(x => x.Clan).HasMaxLength(32);
e.Property(x => x.Gender).HasMaxLength(32);
- e.Property(x => x.Pronouns).HasMaxLength(32);
e.Property(x => x.ArchetypeCsv).HasMaxLength(96);
e.Property(x => x.Quote).HasMaxLength(200);
e.Property(x => x.Boundaries).HasMaxLength(256);
diff --git a/src/Beacon.Server/Data/Entities/ProfileEntity.cs b/src/Beacon.Server/Data/Entities/ProfileEntity.cs
index 7ea2f36..e98c709 100644
--- a/src/Beacon.Server/Data/Entities/ProfileEntity.cs
+++ b/src/Beacon.Server/Data/Entities/ProfileEntity.cs
@@ -37,12 +37,10 @@ public class ProfileEntity
public string? Clan { get; set; }
- public AgeRange Age { get; set; }
+ public int? Age { get; set; }
public string? Gender { get; set; }
- public string? Pronouns { get; set; }
-
/// The three archetype words, pipe-delimited. Not searched, so no join table needed.
public string ArchetypeCsv { get; set; } = string.Empty;
diff --git a/src/Beacon.Server/Data/Migrations/20260921205722_ExactProfileAgeAndRemovePronouns.Designer.cs b/src/Beacon.Server/Data/Migrations/20260921205722_ExactProfileAgeAndRemovePronouns.Designer.cs
new file mode 100644
index 0000000..9eed3f5
--- /dev/null
+++ b/src/Beacon.Server/Data/Migrations/20260921205722_ExactProfileAgeAndRemovePronouns.Designer.cs
@@ -0,0 +1,774 @@
+//
+using System;
+using Beacon.Server.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Beacon.Server.Data.Migrations
+{
+ [DbContext(typeof(BeaconDbContext))]
+ [Migration("20260921205722_ExactProfileAgeAndRemovePronouns")]
+ partial class ExactProfileAgeAndRemovePronouns
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.10");
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.AccountEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AdultConfirmedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("IsBanned")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsModerator")
+ .HasColumnType("INTEGER");
+
+ b.Property("KeyHash")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("LastSeenAt")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("KeyHash")
+ .IsUnique();
+
+ b.ToTable("Accounts");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.BeaconEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AethernetShard")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("AllowPublicLighting")
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("DataCenter")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("TEXT");
+
+ b.Property("FavoriteCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ImageId")
+ .HasColumnType("TEXT");
+
+ b.Property("IsDeleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsLit")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastLitAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("LitAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("LitByAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("LitByName")
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("LitNote")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("LitUntil")
+ .HasColumnType("INTEGER");
+
+ b.Property("MapId")
+ .HasColumnType("INTEGER");
+
+ b.Property("MapX")
+ .HasColumnType("REAL");
+
+ b.Property("MapY")
+ .HasColumnType("REAL");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("NearestAetheryteId")
+ .HasColumnType("INTEGER");
+
+ b.Property("NearestAetheryteName")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("OwnerAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("Region")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("ShareCode")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("SubAreaName")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("TagsCsv")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("TerritoryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TimesLit")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Visibility")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldId")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("X")
+ .HasColumnType("REAL");
+
+ b.Property("Y")
+ .HasColumnType("REAL");
+
+ b.Property("Z")
+ .HasColumnType("REAL");
+
+ b.Property("ZoneName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DataCenter");
+
+ b.HasIndex("OwnerAccountId");
+
+ b.HasIndex("ShareCode")
+ .IsUnique();
+
+ b.HasIndex("TerritoryId");
+
+ b.HasIndex("WorldId");
+
+ b.HasIndex("IsLit", "LitUntil");
+
+ b.HasIndex("IsDeleted", "Visibility", "IsLit");
+
+ b.ToTable("Beacons");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.BeaconImageEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("BeaconId")
+ .HasColumnType("TEXT");
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Height")
+ .HasColumnType("INTEGER");
+
+ b.Property("SizeBytes")
+ .HasColumnType("INTEGER");
+
+ b.Property("UploadedByAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("Width")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BeaconId");
+
+ b.ToTable("Images");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.BeaconLightEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("BeaconId")
+ .HasColumnType("TEXT");
+
+ b.Property("CharacterName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("ExtinguishedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("LitAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("LitUntil")
+ .HasColumnType("INTEGER");
+
+ b.Property("Note")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("Reason")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("BeaconId");
+
+ b.ToTable("Lights");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.CharacterEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("DataCenter")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("LinkedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("WorldId")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("Name", "WorldId")
+ .IsUnique();
+
+ b.ToTable("Characters");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.FavoriteEntity", b =>
+ {
+ b.Property("AccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("BeaconId")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AccountId", "BeaconId");
+
+ b.HasIndex("BeaconId");
+
+ b.ToTable("Favorites");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Age")
+ .HasColumnType("INTEGER");
+
+ b.Property("ArchetypeCsv")
+ .IsRequired()
+ .HasMaxLength(96)
+ .HasColumnType("TEXT");
+
+ b.Property("Availability")
+ .HasColumnType("INTEGER");
+
+ b.Property("Boundaries")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("CharacterName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("Clan")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("DataCenter")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("Gender")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("Goals")
+ .HasMaxLength(1000)
+ .HasColumnType("TEXT");
+
+ b.Property("History")
+ .HasMaxLength(4400)
+ .HasColumnType("TEXT");
+
+ b.Property("IsDeleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsMature")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActiveAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Length")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasMaxLength(600)
+ .HasColumnType("TEXT");
+
+ b.Property("OwnerAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("PlayerAvailability")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("PlayerContact")
+ .HasMaxLength(96)
+ .HasColumnType("TEXT");
+
+ b.Property("PlayerTimezone")
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("PortraitImageId")
+ .HasColumnType("TEXT");
+
+ b.Property("Quote")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("Race")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("ShareCode")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasMaxLength(80)
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Visibility")
+ .HasColumnType("INTEGER");
+
+ b.Property("WalkupsWelcome")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldId")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DataCenter");
+
+ b.HasIndex("OwnerAccountId");
+
+ b.HasIndex("Race");
+
+ b.HasIndex("ShareCode")
+ .IsUnique();
+
+ b.HasIndex("CharacterName", "WorldId")
+ .IsUnique();
+
+ b.HasIndex("IsDeleted", "Visibility", "LastActiveAt");
+
+ b.ToTable("Profiles");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileHookEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("ProfileId")
+ .HasColumnType("TEXT");
+
+ b.Property("Text")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("ProfileHooks");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileImageEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Caption")
+ .HasMaxLength(120)
+ .HasColumnType("TEXT");
+
+ b.Property("Category")
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("ImageId")
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("ProfileId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ImageId");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("ProfileImages");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileLinkEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("ConfirmedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property("Note")
+ .HasMaxLength(160)
+ .HasColumnType("TEXT");
+
+ b.Property("OtherProfileId")
+ .HasColumnType("TEXT");
+
+ b.Property("ProfileId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OtherProfileId");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("ProfileId", "OtherProfileId")
+ .IsUnique();
+
+ b.ToTable("ProfileLinks");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileTagEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property("ProfileId")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("Kind", "Value");
+
+ b.HasIndex("ProfileId", "Kind", "Value")
+ .IsUnique();
+
+ b.ToTable("ProfileTags");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ReportEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("BeaconId")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("ProfileId")
+ .HasColumnType("TEXT");
+
+ b.Property("Reason")
+ .IsRequired()
+ .HasMaxLength(1000)
+ .HasColumnType("TEXT");
+
+ b.Property("ReporterAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("Resolved")
+ .HasColumnType("INTEGER");
+
+ b.Property("ResolvedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("ResolvedByAccountId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BeaconId");
+
+ b.HasIndex("ProfileId");
+
+ b.HasIndex("Resolved", "CreatedAt");
+
+ b.ToTable("Reports");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.BeaconEntity", b =>
+ {
+ b.HasOne("Beacon.Server.Data.Entities.AccountEntity", "Owner")
+ .WithMany("Beacons")
+ .HasForeignKey("OwnerAccountId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.CharacterEntity", b =>
+ {
+ b.HasOne("Beacon.Server.Data.Entities.AccountEntity", "Account")
+ .WithMany("Characters")
+ .HasForeignKey("AccountId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Account");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.FavoriteEntity", b =>
+ {
+ b.HasOne("Beacon.Server.Data.Entities.BeaconEntity", "Beacon")
+ .WithMany("Favorites")
+ .HasForeignKey("BeaconId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Beacon");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileEntity", b =>
+ {
+ b.HasOne("Beacon.Server.Data.Entities.AccountEntity", "Owner")
+ .WithMany()
+ .HasForeignKey("OwnerAccountId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Owner");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileHookEntity", b =>
+ {
+ b.HasOne("Beacon.Server.Data.Entities.ProfileEntity", "Profile")
+ .WithMany("Hooks")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileImageEntity", b =>
+ {
+ b.HasOne("Beacon.Server.Data.Entities.ProfileEntity", "Profile")
+ .WithMany("Images")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileLinkEntity", b =>
+ {
+ b.HasOne("Beacon.Server.Data.Entities.ProfileEntity", "Profile")
+ .WithMany()
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileTagEntity", b =>
+ {
+ b.HasOne("Beacon.Server.Data.Entities.ProfileEntity", "Profile")
+ .WithMany("Tags")
+ .HasForeignKey("ProfileId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Profile");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.AccountEntity", b =>
+ {
+ b.Navigation("Beacons");
+
+ b.Navigation("Characters");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.BeaconEntity", b =>
+ {
+ b.Navigation("Favorites");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileEntity", b =>
+ {
+ b.Navigation("Hooks");
+
+ b.Navigation("Images");
+
+ b.Navigation("Tags");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Beacon.Server/Data/Migrations/20260921205722_ExactProfileAgeAndRemovePronouns.cs b/src/Beacon.Server/Data/Migrations/20260921205722_ExactProfileAgeAndRemovePronouns.cs
new file mode 100644
index 0000000..a096367
--- /dev/null
+++ b/src/Beacon.Server/Data/Migrations/20260921205722_ExactProfileAgeAndRemovePronouns.cs
@@ -0,0 +1,48 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Beacon.Server.Data.Migrations
+{
+ ///
+ public partial class ExactProfileAgeAndRemovePronouns : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "Pronouns",
+ table: "Profiles");
+
+ migrationBuilder.AlterColumn(
+ name: "Age",
+ table: "Profiles",
+ type: "INTEGER",
+ nullable: true,
+ oldClrType: typeof(int),
+ oldType: "INTEGER");
+
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterColumn(
+ name: "Age",
+ table: "Profiles",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0,
+ oldClrType: typeof(int),
+ oldType: "INTEGER",
+ oldNullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "Pronouns",
+ table: "Profiles",
+ type: "TEXT",
+ maxLength: 32,
+ nullable: true);
+ }
+ }
+}
diff --git a/src/Beacon.Server/Data/Migrations/20260921210236_ClearLegacyAgeCategories.Designer.cs b/src/Beacon.Server/Data/Migrations/20260921210236_ClearLegacyAgeCategories.Designer.cs
new file mode 100644
index 0000000..33895e3
--- /dev/null
+++ b/src/Beacon.Server/Data/Migrations/20260921210236_ClearLegacyAgeCategories.Designer.cs
@@ -0,0 +1,774 @@
+//
+using System;
+using Beacon.Server.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Beacon.Server.Data.Migrations
+{
+ [DbContext(typeof(BeaconDbContext))]
+ [Migration("20260921210236_ClearLegacyAgeCategories")]
+ partial class ClearLegacyAgeCategories
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.10");
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.AccountEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AdultConfirmedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("IsBanned")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsModerator")
+ .HasColumnType("INTEGER");
+
+ b.Property("KeyHash")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("LastSeenAt")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("KeyHash")
+ .IsUnique();
+
+ b.ToTable("Accounts");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.BeaconEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AethernetShard")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("AllowPublicLighting")
+ .HasColumnType("INTEGER");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("DataCenter")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(4000)
+ .HasColumnType("TEXT");
+
+ b.Property("FavoriteCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ImageId")
+ .HasColumnType("TEXT");
+
+ b.Property("IsDeleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsLit")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastLitAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("LitAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("LitByAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("LitByName")
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("LitNote")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("LitUntil")
+ .HasColumnType("INTEGER");
+
+ b.Property("MapId")
+ .HasColumnType("INTEGER");
+
+ b.Property("MapX")
+ .HasColumnType("REAL");
+
+ b.Property("MapY")
+ .HasColumnType("REAL");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("NearestAetheryteId")
+ .HasColumnType("INTEGER");
+
+ b.Property("NearestAetheryteName")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("OwnerAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("Region")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("ShareCode")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("SubAreaName")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("TagsCsv")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("TerritoryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TimesLit")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Visibility")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldId")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("X")
+ .HasColumnType("REAL");
+
+ b.Property("Y")
+ .HasColumnType("REAL");
+
+ b.Property("Z")
+ .HasColumnType("REAL");
+
+ b.Property("ZoneName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DataCenter");
+
+ b.HasIndex("OwnerAccountId");
+
+ b.HasIndex("ShareCode")
+ .IsUnique();
+
+ b.HasIndex("TerritoryId");
+
+ b.HasIndex("WorldId");
+
+ b.HasIndex("IsLit", "LitUntil");
+
+ b.HasIndex("IsDeleted", "Visibility", "IsLit");
+
+ b.ToTable("Beacons");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.BeaconImageEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("BeaconId")
+ .HasColumnType("TEXT");
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Height")
+ .HasColumnType("INTEGER");
+
+ b.Property("SizeBytes")
+ .HasColumnType("INTEGER");
+
+ b.Property("UploadedByAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("Width")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BeaconId");
+
+ b.ToTable("Images");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.BeaconLightEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("BeaconId")
+ .HasColumnType("TEXT");
+
+ b.Property("CharacterName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("ExtinguishedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("LitAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("LitUntil")
+ .HasColumnType("INTEGER");
+
+ b.Property("Note")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("Reason")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("BeaconId");
+
+ b.ToTable("Lights");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.CharacterEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("DataCenter")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("LinkedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("WorldId")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccountId");
+
+ b.HasIndex("Name", "WorldId")
+ .IsUnique();
+
+ b.ToTable("Characters");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.FavoriteEntity", b =>
+ {
+ b.Property("AccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("BeaconId")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AccountId", "BeaconId");
+
+ b.HasIndex("BeaconId");
+
+ b.ToTable("Favorites");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Age")
+ .HasColumnType("INTEGER");
+
+ b.Property("ArchetypeCsv")
+ .IsRequired()
+ .HasMaxLength(96)
+ .HasColumnType("TEXT");
+
+ b.Property("Availability")
+ .HasColumnType("INTEGER");
+
+ b.Property("Boundaries")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("CharacterName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("Clan")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("DataCenter")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("Gender")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("Goals")
+ .HasMaxLength(1000)
+ .HasColumnType("TEXT");
+
+ b.Property("History")
+ .HasMaxLength(4400)
+ .HasColumnType("TEXT");
+
+ b.Property("IsDeleted")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsMature")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActiveAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Length")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("Overview")
+ .HasMaxLength(600)
+ .HasColumnType("TEXT");
+
+ b.Property("OwnerAccountId")
+ .HasColumnType("TEXT");
+
+ b.Property("PlayerAvailability")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("PlayerContact")
+ .HasMaxLength(96)
+ .HasColumnType("TEXT");
+
+ b.Property("PlayerTimezone")
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.Property("PortraitImageId")
+ .HasColumnType("TEXT");
+
+ b.Property("Quote")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.Property("Race")
+ .HasMaxLength(32)
+ .HasColumnType("TEXT");
+
+ b.Property("ShareCode")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasMaxLength(80)
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("INTEGER");
+
+ b.Property("Visibility")
+ .HasColumnType("INTEGER");
+
+ b.Property("WalkupsWelcome")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldId")
+ .HasColumnType("INTEGER");
+
+ b.Property("WorldName")
+ .IsRequired()
+ .HasMaxLength(48)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DataCenter");
+
+ b.HasIndex("OwnerAccountId");
+
+ b.HasIndex("Race");
+
+ b.HasIndex("ShareCode")
+ .IsUnique();
+
+ b.HasIndex("CharacterName", "WorldId")
+ .IsUnique();
+
+ b.HasIndex("IsDeleted", "Visibility", "LastActiveAt");
+
+ b.ToTable("Profiles");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileHookEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("ProfileId")
+ .HasColumnType("TEXT");
+
+ b.Property("Text")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ProfileId");
+
+ b.ToTable("ProfileHooks");
+ });
+
+ modelBuilder.Entity("Beacon.Server.Data.Entities.ProfileImageEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Caption")
+ .HasMaxLength(120)
+ .HasColumnType("TEXT");
+
+ b.Property("Category")
+ .HasColumnType("INTEGER");
+
+ b.Property