From 08c3af09549099a2b912bda144f8546309ea95eb Mon Sep 17 00:00:00 2001 From: Nathan C Date: Mon, 10 Mar 2025 20:02:03 -0400 Subject: [PATCH] add ottergui, update for recent changes --- .gitmodules | 3 + Data/.gitkeep | 0 Lib/OtterGui | 1 + QuestShare.Common/API/SessionStart.cs | 26 +++ QuestShare.Common/Errors.cs | 28 +++ QuestShare.Common/Interfaces.cs | 27 +++ QuestShare.Common/Objects.cs | 44 +++++ QuestShare.Dalamud/GlobalSuppressions.cs | 8 + QuestShare.Dalamud/QuestShare.csproj | 1 + QuestShare.Dalamud/Service.cs | 2 + .../Services/API/SessionStart.cs | 40 +++++ QuestShare.Dalamud/Services/HostService.cs | 129 +++++++++++++ QuestShare.Dalamud/packages.lock.json | 12 ++ .../Hubs/Methods/SessionStart.cs | 71 ++++++++ .../20250217022911_InitialCreate.cs | 133 -------------- .../20250217033300_RefactorQuests.Designer.cs | 148 --------------- .../20250217033300_RefactorQuests.cs | 84 --------- ....cs => 20250301212836_Initial.Designer.cs} | 158 +++++++++------- .../Migrations/20250301212836_Initial.cs | 170 ++++++++++++++++++ QuestShare.Server/Models/SessionMember.cs | 31 ++++ QuestShare.Server/Program.cs | 5 - QuestShare.sln | 6 + 22 files changed, 697 insertions(+), 430 deletions(-) create mode 100644 .gitmodules delete mode 100644 Data/.gitkeep create mode 160000 Lib/OtterGui create mode 100644 QuestShare.Common/API/SessionStart.cs create mode 100644 QuestShare.Common/Errors.cs create mode 100644 QuestShare.Common/Interfaces.cs create mode 100644 QuestShare.Common/Objects.cs create mode 100644 QuestShare.Dalamud/GlobalSuppressions.cs create mode 100644 QuestShare.Dalamud/Services/API/SessionStart.cs create mode 100644 QuestShare.Dalamud/Services/HostService.cs create mode 100644 QuestShare.Server/Hubs/Methods/SessionStart.cs delete mode 100644 QuestShare.Server/Migrations/20250217022911_InitialCreate.cs delete mode 100644 QuestShare.Server/Migrations/20250217033300_RefactorQuests.Designer.cs delete mode 100644 QuestShare.Server/Migrations/20250217033300_RefactorQuests.cs rename QuestShare.Server/Migrations/{20250217022911_InitialCreate.Designer.cs => 20250301212836_Initial.Designer.cs} (57%) create mode 100644 QuestShare.Server/Migrations/20250301212836_Initial.cs create mode 100644 QuestShare.Server/Models/SessionMember.cs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8999563 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Lib/OtterGui"] + path = Lib/OtterGui + url = https://github.com/Ottermandias/OtterGui.git diff --git a/Data/.gitkeep b/Data/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Lib/OtterGui b/Lib/OtterGui new file mode 160000 index 0000000..13f1a90 --- /dev/null +++ b/Lib/OtterGui @@ -0,0 +1 @@ +Subproject commit 13f1a90b88d2b8572480748a209f957b70d6a46f diff --git a/QuestShare.Common/API/SessionStart.cs b/QuestShare.Common/API/SessionStart.cs new file mode 100644 index 0000000..c84853f --- /dev/null +++ b/QuestShare.Common/API/SessionStart.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuestShare.Common.API +{ + public sealed class SessionStart : IAPIEndpoint + { + public class Request : IRequest + { + public Request() { } + public string Version { get; set; } = null!; + public string Token { get; set; } = null!; + public Objects.OwnedSession Session { get; set; } = null!; + } + public class Response : IResponse + { + public Response() { } + public Error Error { get; set; } = Error.None; + public required bool Success { get; set; } + public Objects.OwnedSession? Session { get; set; } + } + } +} diff --git a/QuestShare.Common/Errors.cs b/QuestShare.Common/Errors.cs new file mode 100644 index 0000000..45356eb --- /dev/null +++ b/QuestShare.Common/Errors.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuestShare.Common +{ + public enum Error + { + None, + InvalidToken, + InvalidSession, + InvalidVersion, + InvalidQuests, + JoinRequestFailed, + Unauthorized, + UnknownError, + InternalServerError, + InvalidNotifyType, + InvalidParty, + InvalidMember, + InvalidQuest, + BannedTooManyBadRequests, + AlreadyRegistered, + AlreadyJoined, + } +} diff --git a/QuestShare.Common/Interfaces.cs b/QuestShare.Common/Interfaces.cs new file mode 100644 index 0000000..5b5e298 --- /dev/null +++ b/QuestShare.Common/Interfaces.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuestShare.Common +{ + public interface IRequest + { + string Version { get; set; } + string Token { get; set; } + } + + public interface IResponse + { + Error Error { get; set; } + bool Success { get; set; } + } + + public interface IAPIEndpoint where T1 : IRequest where T2 : IResponse; + + public interface IBroadcast + { + string Message { get; set; } + } +} diff --git a/QuestShare.Common/Objects.cs b/QuestShare.Common/Objects.cs new file mode 100644 index 0000000..e81c3cf --- /dev/null +++ b/QuestShare.Common/Objects.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuestShare.Common +{ + public abstract class Objects + { + public record ShareCode + { + public required string Code { get; set; } + public required string CharacterId { get; set; } + } + + public record Session + { + public required string ShareCode { get; set; } + public required string OwnerCharacterId { get; set; } + public int ActiveQuestId { get; set; } + public byte ActiveQuestStep { get; set; } + } + + public record OwnedSession + { + public required Session Session { get; set; } + public string ShareCode => Session.ShareCode; + + [JsonIgnore] + public string OwnerCharacterId => Session.OwnerCharacterId; + + [JsonIgnore] + public int ActiveQuestId => Session.ActiveQuestId; + + [JsonIgnore] + public byte ActiveQuestStep => Session.ActiveQuestStep; + public required bool AllowJoins { get; set; } + public required bool SkipPartyCheck { get; set; } + public required bool IsActive { get; set; } + } + } +} diff --git a/QuestShare.Dalamud/GlobalSuppressions.cs b/QuestShare.Dalamud/GlobalSuppressions.cs new file mode 100644 index 0000000..ae83d15 --- /dev/null +++ b/QuestShare.Dalamud/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "", Scope = "member", Target = "~E:QuestShare.Services.ApiService.GroupJoined")] diff --git a/QuestShare.Dalamud/QuestShare.csproj b/QuestShare.Dalamud/QuestShare.csproj index 6bff34b..a327205 100644 --- a/QuestShare.Dalamud/QuestShare.csproj +++ b/QuestShare.Dalamud/QuestShare.csproj @@ -13,6 +13,7 @@ + diff --git a/QuestShare.Dalamud/Service.cs b/QuestShare.Dalamud/Service.cs index 9649ad5..cb9b4a7 100644 --- a/QuestShare.Dalamud/Service.cs +++ b/QuestShare.Dalamud/Service.cs @@ -1,6 +1,7 @@ using Dalamud.IoC; using Dalamud.Plugin.Services; using Dalamud.Plugin; +using Dalamud.Game.ClientState.Objects; namespace QuestShare { @@ -17,5 +18,6 @@ namespace QuestShare [PluginService] internal static IPartyList PartyList { get; private set; } = null!; [PluginService] internal static IAddonLifecycle AddonLifecycle { get; private set; } = null!; [PluginService] internal static IChatGui ChatGui { get; private set; } = null!; + [PluginService] internal static ITargetManager Target { get; private set; } = null!; } } diff --git a/QuestShare.Dalamud/Services/API/SessionStart.cs b/QuestShare.Dalamud/Services/API/SessionStart.cs new file mode 100644 index 0000000..42f5d16 --- /dev/null +++ b/QuestShare.Dalamud/Services/API/SessionStart.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuestShare.Services.API +{ + internal class SessionStart_Client + { + public static void HandleDispatch(Objects.OwnedSession session) + { + var api = (ApiService)Plugin.GetService(); + var request = new SessionStart.Request + { + Version = Constants.Version, + Token = ConfigurationManager.Instance.Token, + Session = session + }; + _ = api.Invoke(nameof(SessionStart), request); + } + + public static void HandleResponse(SessionStart.Response response) + { + if (response.Success) + { + Log.Information("Session started successfully"); + ConfigurationManager.Instance.OwnedSession = response.Session; + ConfigurationManager.Save(); + HostService.UpdateParty(); + } + else + { + Log.Error($"Failed to start session: {response.Error}"); + UiService.LastErrorMessage = $"Failed to start session: {response.Error}"; + _ = ((ApiService)Plugin.GetService()).Disconnect(); + } + } + } +} diff --git a/QuestShare.Dalamud/Services/HostService.cs b/QuestShare.Dalamud/Services/HostService.cs new file mode 100644 index 0000000..0487ece --- /dev/null +++ b/QuestShare.Dalamud/Services/HostService.cs @@ -0,0 +1,129 @@ +using Dalamud.Plugin.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QuestShare.Services +{ + internal class HostService : IService + { + internal static Objects.OwnedSession? Session => ConfigurationManager.Instance.OwnedSession; + internal static bool HostEnabled => ConfigurationManager.Instance.EnableHosting; + internal static Objects.Session? ActiveSession => Session?.Session; + internal static int ActiveQuestId => ActiveSession?.ActiveQuestId ?? 0; + internal static byte ActiveQuestStep => ActiveSession?.ActiveQuestStep ?? 0; + internal static bool IsHost => ActiveSession != null; + internal static bool IsActive => Session?.IsActive ?? false; + internal static bool AllowJoins => Session?.AllowJoins ?? true; + internal static bool SkipPartyCheck => Session?.SkipPartyCheck ?? false; + private ulong CharacterId { get; set; } + + public void Initialize() + { + ClientState.Login += OnLogin; + ClientState.Logout += OnLogout; + } + + public void Shutdown() + { + ClientState.Login -= OnLogin; + ClientState.Logout -= OnLogout; + } + + private void OnLogin() + { + CharacterId = ClientState.LocalContentId; + } + + private void OnLogout(int _, int __) + { + CharacterId = 0; + } + + public void Start(string shareCode) + { + var session = new Objects.Session { OwnerCharacterId = CharacterId.ToString().SaltedHash(shareCode), ShareCode = shareCode, ActiveQuestId = ActiveQuestId, ActiveQuestStep = ActiveQuestStep }; + var ownedSession = new Objects.OwnedSession { + AllowJoins = true, + IsActive = true, + SkipPartyCheck = false, + Session = session, + }; + ApiService.DispatchSessionStart(ownedSession); + } + + public static void Update(int questId, byte questStep) + { + if (ActiveSession == null) + { + return; + } + ActiveSession.ActiveQuestId = questId; + ActiveSession.ActiveQuestStep = questStep; + Session!.Session = ActiveSession; + var party = (PartyService)Plugin.GetService(); + var members = party.GetPartyMembers(ActiveSession); + ApiService.DispatchUpdate(Session, members); + } + + private void ConfigChange() + { + var party = (PartyService)Plugin.GetService(); + var members = party.GetPartyMembers(ActiveSession!); + ApiService.DispatchConfigChange(Session!, members); + } + + public static void UpdateParty() + { + if (ActiveSession == null) + { + return; + } + var party = (PartyService)Plugin.GetService(); + var members = party.GetPartyMembers(ActiveSession); + ApiService.DispatchConfigChange(Session!, members); + } + + public void SetIsActive(bool isActive) + { + if (Session == null || ActiveSession == null) + { + return; + } + Session.IsActive = isActive; + ConfigChange(); + } + + public void SetAllowJoins(bool allowJoins) + { + if (Session == null || ActiveSession == null) + { + return; + } + Session.AllowJoins = allowJoins; + ConfigChange(); + } + + public void SetSkipPartyCheck(bool skipPartyCheck) + { + if (Session == null || ActiveSession == null) + { + return; + } + Session.SkipPartyCheck = skipPartyCheck; + ConfigChange(); + } + + public static void Cancel() + { + if (ActiveSession == null) + { + return; + } + var api = (ApiService)Plugin.GetService(); + ApiService.DispatchCancel(); + } + } +} diff --git a/QuestShare.Dalamud/packages.lock.json b/QuestShare.Dalamud/packages.lock.json index 2685015..e3456cf 100644 --- a/QuestShare.Dalamud/packages.lock.json +++ b/QuestShare.Dalamud/packages.lock.json @@ -49,6 +49,11 @@ "System.Text.Json": "9.0.2" } }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "2024.3.0", + "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + }, "Microsoft.AspNetCore.Connections.Abstractions": { "type": "Transitive", "resolved": "8.0.13", @@ -232,6 +237,13 @@ "resolved": "8.0.0", "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==" }, + "ottergui": { + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2024.3.0, )", + "Microsoft.Extensions.DependencyInjection": "[9.0.2, )" + } + }, "questshare.common": { "type": "Project", "dependencies": { diff --git a/QuestShare.Server/Hubs/Methods/SessionStart.cs b/QuestShare.Server/Hubs/Methods/SessionStart.cs new file mode 100644 index 0000000..d5db451 --- /dev/null +++ b/QuestShare.Server/Hubs/Methods/SessionStart.cs @@ -0,0 +1,71 @@ +using Microsoft.AspNetCore.SignalR; +using QuestShare.Common; +using QuestShare.Server.Managers; + +namespace QuestShare.Server.Hubs +{ + public partial class ShareHub : Hub + { + [HubMethodName(nameof(SessionStart))] + public async Task Server_SessionStart(SessionStart.Request request) + { + if (BanManager.IsBanned(Context)) + { + Log.Error($"[AUTHORIZE] Client {Context.ConnectionId} is banned."); + Context.Abort(); + return; + } + var error = Error.None; + var client = await ClientManager.GetClient(Context.ConnectionId, request.Token); + if (client == null) error = Error.Unauthorized; + var session = await SessionManager.GetSession(request.Session.Session.ShareCode); + if (session == null) error = Error.InvalidSession; + else + { + if (Context.ConnectionId != session.ReservedConnectionId) + { + error = Error.InvalidSession; + } + } + if (error != Error.None) + { + await Clients.Caller.SendAsync(nameof(SessionStart), new SessionStart.Response + { + Error = error, + Success = false + }); + return; + } + var s = await SessionManager.CreateSession(client!, Context.ConnectionId, request.Session); + if (s == null) + { + Log.Error($"[SessionStart] Unable to create session for {client!.Token} and {request.Session.Session.ShareCode}"); + await Clients.Caller.SendAsync(nameof(SessionStart), new SessionStart.Response + { + Error = Error.InternalServerError, + Success = false + }); + } else { + Log.Debug($"[SessionStart] Created session {s.ShareCode} for {client!.Token}"); + var sObject = new Objects.Session + { + OwnerCharacterId = s.OwnerCharacterId, + ShareCode = s.ShareCode, + }; + var ownedSession = new Objects.OwnedSession + { + IsActive = s.IsActive, + SkipPartyCheck = s.SkipPartyCheck, + AllowJoins = s.AllowJoins, + Session = sObject, + }; + await Clients.Caller.SendAsync(nameof(SessionStart), new SessionStart.Response + { + Error = Error.None, + Success = true, + Session = ownedSession + }); + } + } + } +} diff --git a/QuestShare.Server/Migrations/20250217022911_InitialCreate.cs b/QuestShare.Server/Migrations/20250217022911_InitialCreate.cs deleted file mode 100644 index ffd0da7..0000000 --- a/QuestShare.Server/Migrations/20250217022911_InitialCreate.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Net; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace QuestShare.Server.Migrations -{ - /// - public partial class InitialCreate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Bans", - columns: table => new - { - BanId = table.Column(type: "text", nullable: false), - BanIp = table.Column(type: "inet", nullable: false), - BanCharacterId = table.Column(type: "numeric(20,0)", nullable: false), - BanReason = table.Column(type: "text", nullable: false), - BanIssuer = table.Column(type: "text", nullable: false), - BanDate = table.Column(type: "timestamp with time zone", nullable: false), - BanExpiry = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Bans", x => x.BanId); - }); - - migrationBuilder.CreateTable( - name: "Clients", - columns: table => new - { - ClientId = table.Column(type: "uuid", nullable: false), - CharacterId = table.Column(type: "numeric(20,0)", nullable: false), - ConnectionId = table.Column(type: "text", nullable: false), - Token = table.Column(type: "text", nullable: false), - ConnectedShareCode = table.Column(type: "text", nullable: false), - Created = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp"), - LastUpdated = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp") - }, - constraints: table => - { - table.PrimaryKey("PK_Clients", x => x.ClientId); - }); - - migrationBuilder.CreateTable( - name: "QuestShares", - columns: table => new - { - ShareId = table.Column(type: "uuid", nullable: false), - ShareHostClientId = table.Column(type: "uuid", nullable: false), - ShareCode = table.Column(type: "text", nullable: false), - BroadcastParty = table.Column(type: "boolean", nullable: false), - Created = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp"), - LastUpdated = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp") - }, - constraints: table => - { - table.PrimaryKey("PK_QuestShares", x => x.ShareId); - table.ForeignKey( - name: "FK_QuestShares_Clients_ShareHostClientId", - column: x => x.ShareHostClientId, - principalTable: "Clients", - principalColumn: "ClientId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Quests", - columns: table => new - { - QuestObjId = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - QuestId = table.Column(type: "bigint", nullable: false), - CurrentStep = table.Column(type: "smallint", nullable: false), - IsActive = table.Column(type: "boolean", nullable: false), - ShareId1 = table.Column(type: "uuid", nullable: false), - ShareId = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Quests", x => x.QuestObjId); - table.ForeignKey( - name: "FK_Quests_QuestShares_ShareId", - column: x => x.ShareId, - principalTable: "QuestShares", - principalColumn: "ShareId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Quests_QuestShares_ShareId1", - column: x => x.ShareId1, - principalTable: "QuestShares", - principalColumn: "ShareId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Quests_ShareId", - table: "Quests", - column: "ShareId"); - - migrationBuilder.CreateIndex( - name: "IX_Quests_ShareId1", - table: "Quests", - column: "ShareId1"); - - migrationBuilder.CreateIndex( - name: "IX_QuestShares_ShareHostClientId", - table: "QuestShares", - column: "ShareHostClientId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Bans"); - - migrationBuilder.DropTable( - name: "Quests"); - - migrationBuilder.DropTable( - name: "QuestShares"); - - migrationBuilder.DropTable( - name: "Clients"); - } - } -} diff --git a/QuestShare.Server/Migrations/20250217033300_RefactorQuests.Designer.cs b/QuestShare.Server/Migrations/20250217033300_RefactorQuests.Designer.cs deleted file mode 100644 index 277ccc1..0000000 --- a/QuestShare.Server/Migrations/20250217033300_RefactorQuests.Designer.cs +++ /dev/null @@ -1,148 +0,0 @@ -// -using System; -using System.Net; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using QuestShare.Server.Models; - -#nullable disable - -namespace QuestShare.Server.Migrations -{ - [DbContext(typeof(QuestShareContext))] - [Migration("20250217033300_RefactorQuests")] - partial class RefactorQuests - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.2") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("QuestShare.Server.Models.Ban", b => - { - b.Property("BanId") - .HasColumnType("text"); - - b.Property("BanCharacterId") - .HasColumnType("numeric(20,0)"); - - b.Property("BanDate") - .HasColumnType("timestamp with time zone"); - - b.Property("BanExpiry") - .HasColumnType("timestamp with time zone"); - - b.Property("BanIp") - .IsRequired() - .HasColumnType("inet"); - - b.Property("BanIssuer") - .IsRequired() - .HasColumnType("text"); - - b.Property("BanReason") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("BanId"); - - b.ToTable("Bans", (string)null); - }); - - modelBuilder.Entity("QuestShare.Server.Models.Client", b => - { - b.Property("ClientId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CharacterId") - .HasColumnType("numeric(20,0)"); - - b.Property("ConnectedShareCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ConnectionId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("current_timestamp"); - - b.Property("LastUpdated") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("current_timestamp"); - - b.Property("Token") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("ClientId"); - - b.ToTable("Clients", (string)null); - }); - - modelBuilder.Entity("QuestShare.Server.Models.Share", b => - { - b.Property("ShareId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("BroadcastParty") - .HasColumnType("boolean"); - - b.Property("Created") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("current_timestamp"); - - b.Property("LastUpdated") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("current_timestamp"); - - b.Property("ShareCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShareHostClientId") - .HasColumnType("uuid"); - - b.Property("SharedQuestId") - .HasColumnType("bigint"); - - b.Property("SharedQuestStep") - .HasColumnType("smallint"); - - b.HasKey("ShareId"); - - b.HasIndex("ShareHostClientId"); - - b.ToTable("QuestShares", (string)null); - }); - - modelBuilder.Entity("QuestShare.Server.Models.Share", b => - { - b.HasOne("QuestShare.Server.Models.Client", "ShareHost") - .WithMany() - .HasForeignKey("ShareHostClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ShareHost"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/QuestShare.Server/Migrations/20250217033300_RefactorQuests.cs b/QuestShare.Server/Migrations/20250217033300_RefactorQuests.cs deleted file mode 100644 index d0ffe6c..0000000 --- a/QuestShare.Server/Migrations/20250217033300_RefactorQuests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace QuestShare.Server.Migrations -{ - /// - public partial class RefactorQuests : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Quests"); - - migrationBuilder.AddColumn( - name: "SharedQuestId", - table: "QuestShares", - type: "bigint", - nullable: false, - defaultValue: 0L); - - migrationBuilder.AddColumn( - name: "SharedQuestStep", - table: "QuestShares", - type: "smallint", - nullable: false, - defaultValue: (byte)0); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "SharedQuestId", - table: "QuestShares"); - - migrationBuilder.DropColumn( - name: "SharedQuestStep", - table: "QuestShares"); - - migrationBuilder.CreateTable( - name: "Quests", - columns: table => new - { - QuestObjId = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ShareId1 = table.Column(type: "uuid", nullable: false), - CurrentStep = table.Column(type: "smallint", nullable: false), - IsActive = table.Column(type: "boolean", nullable: false), - QuestId = table.Column(type: "bigint", nullable: false), - ShareId = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Quests", x => x.QuestObjId); - table.ForeignKey( - name: "FK_Quests_QuestShares_ShareId", - column: x => x.ShareId, - principalTable: "QuestShares", - principalColumn: "ShareId", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Quests_QuestShares_ShareId1", - column: x => x.ShareId1, - principalTable: "QuestShares", - principalColumn: "ShareId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Quests_ShareId", - table: "Quests", - column: "ShareId"); - - migrationBuilder.CreateIndex( - name: "IX_Quests_ShareId1", - table: "Quests", - column: "ShareId1"); - } - } -} diff --git a/QuestShare.Server/Migrations/20250217022911_InitialCreate.Designer.cs b/QuestShare.Server/Migrations/20250301212836_Initial.Designer.cs similarity index 57% rename from QuestShare.Server/Migrations/20250217022911_InitialCreate.Designer.cs rename to QuestShare.Server/Migrations/20250301212836_Initial.Designer.cs index 442ac6d..74071cb 100644 --- a/QuestShare.Server/Migrations/20250217022911_InitialCreate.Designer.cs +++ b/QuestShare.Server/Migrations/20250301212836_Initial.Designer.cs @@ -13,8 +13,8 @@ using QuestShare.Server.Models; namespace QuestShare.Server.Migrations { [DbContext(typeof(QuestShareContext))] - [Migration("20250217022911_InitialCreate")] - partial class InitialCreate + [Migration("20250301212836_Initial")] + partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -63,13 +63,6 @@ namespace QuestShare.Server.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property("CharacterId") - .HasColumnType("numeric(20,0)"); - - b.Property("ConnectedShareCode") - .IsRequired() - .HasColumnType("text"); - b.Property("ConnectionId") .IsRequired() .HasColumnType("text"); @@ -79,63 +72,102 @@ namespace QuestShare.Server.Migrations .HasColumnType("timestamp with time zone") .HasDefaultValueSql("current_timestamp"); + b.Property("KnownShareCodes") + .IsRequired() + .HasColumnType("text"); + b.Property("LastUpdated") .ValueGeneratedOnAddOrUpdate() .HasColumnType("timestamp with time zone") .HasDefaultValueSql("current_timestamp"); + b.Property("SessionMemberClientSessionId") + .HasColumnType("uuid"); + b.Property("Token") .IsRequired() .HasColumnType("text"); b.HasKey("ClientId"); + b.HasIndex("SessionMemberClientSessionId"); + b.ToTable("Clients", (string)null); }); - modelBuilder.Entity("QuestShare.Server.Models.Quest", b => + modelBuilder.Entity("QuestShare.Server.Models.Session", b => { - b.Property("QuestObjId") + b.Property("SessionId") .ValueGeneratedOnAdd() - .HasColumnType("integer"); + .HasColumnType("uuid"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("QuestObjId")); + b.Property("AllowJoins") + .HasColumnType("boolean"); - b.Property("CurrentStep") - .HasColumnType("smallint"); + b.Property("Created") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp"); b.Property("IsActive") .HasColumnType("boolean"); - b.Property("QuestId") - .HasColumnType("bigint"); + b.Property("LastUpdated") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("current_timestamp"); - b.Property("ShareId") + b.Property("OwnerCharacterId") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerClientId") .HasColumnType("uuid"); - b.Property("ShareId1") + b.Property("PartyMembers") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReservedConnectionId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SessionMemberClientSessionId") .HasColumnType("uuid"); - b.HasKey("QuestObjId"); + b.Property("ShareCode") + .IsRequired() + .HasColumnType("text"); - b.HasIndex("ShareId"); + b.Property("SharedQuestId") + .HasColumnType("integer"); - b.HasIndex("ShareId1"); + b.Property("SharedQuestStep") + .HasColumnType("smallint"); - b.ToTable("Quests", (string)null); - }); - - modelBuilder.Entity("QuestShare.Server.Models.Share", b => - { - b.Property("ShareId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("BroadcastParty") + b.Property("SkipPartyCheck") .HasColumnType("boolean"); - b.Property("Created") + b.HasKey("SessionId"); + + b.HasIndex("OwnerClientId"); + + b.HasIndex("SessionMemberClientSessionId"); + + b.ToTable("Sessions", (string)null); + }); + + modelBuilder.Entity("QuestShare.Server.Models.SessionMember", b => + { + b.Property("ClientSessionId") .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("Created") + .ValueGeneratedOnAddOrUpdate() .HasColumnType("timestamp with time zone") .HasDefaultValueSql("current_timestamp"); @@ -144,51 +176,57 @@ namespace QuestShare.Server.Migrations .HasColumnType("timestamp with time zone") .HasDefaultValueSql("current_timestamp"); - b.Property("ShareCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShareHostClientId") + b.Property("SessionId") .HasColumnType("uuid"); - b.HasKey("ShareId"); + b.HasKey("ClientSessionId"); - b.HasIndex("ShareHostClientId"); + b.HasIndex("ClientId"); - b.ToTable("QuestShares", (string)null); + b.HasIndex("SessionId"); + + b.ToTable("SessionMembers", (string)null); }); - modelBuilder.Entity("QuestShare.Server.Models.Quest", b => + modelBuilder.Entity("QuestShare.Server.Models.Client", b => { - b.HasOne("QuestShare.Server.Models.Share", null) - .WithMany("Quests") - .HasForeignKey("ShareId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("QuestShare.Server.Models.Share", "Share") + b.HasOne("QuestShare.Server.Models.SessionMember", null) .WithMany() - .HasForeignKey("ShareId1") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Share"); + .HasForeignKey("SessionMemberClientSessionId"); }); - modelBuilder.Entity("QuestShare.Server.Models.Share", b => + modelBuilder.Entity("QuestShare.Server.Models.Session", b => { - b.HasOne("QuestShare.Server.Models.Client", "ShareHost") + b.HasOne("QuestShare.Server.Models.Client", "Owner") .WithMany() - .HasForeignKey("ShareHostClientId") + .HasForeignKey("OwnerClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("ShareHost"); + b.HasOne("QuestShare.Server.Models.SessionMember", null) + .WithMany() + .HasForeignKey("SessionMemberClientSessionId"); + + b.Navigation("Owner"); }); - modelBuilder.Entity("QuestShare.Server.Models.Share", b => + modelBuilder.Entity("QuestShare.Server.Models.SessionMember", b => { - b.Navigation("Quests"); + b.HasOne("QuestShare.Server.Models.Client", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("QuestShare.Server.Models.Session", "Session") + .WithMany() + .HasForeignKey("SessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Session"); }); #pragma warning restore 612, 618 } diff --git a/QuestShare.Server/Migrations/20250301212836_Initial.cs b/QuestShare.Server/Migrations/20250301212836_Initial.cs new file mode 100644 index 0000000..9dcf87a --- /dev/null +++ b/QuestShare.Server/Migrations/20250301212836_Initial.cs @@ -0,0 +1,170 @@ +using System; +using System.Net; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace QuestShare.Server.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Bans", + columns: table => new + { + BanId = table.Column(type: "text", nullable: false), + BanIp = table.Column(type: "inet", nullable: false), + BanCharacterId = table.Column(type: "numeric(20,0)", nullable: false), + BanReason = table.Column(type: "text", nullable: false), + BanIssuer = table.Column(type: "text", nullable: false), + BanDate = table.Column(type: "timestamp with time zone", nullable: false), + BanExpiry = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Bans", x => x.BanId); + }); + + migrationBuilder.CreateTable( + name: "Clients", + columns: table => new + { + ClientId = table.Column(type: "uuid", nullable: false), + ConnectionId = table.Column(type: "text", nullable: false), + Token = table.Column(type: "text", nullable: false), + KnownShareCodes = table.Column(type: "text", nullable: false), + Created = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp"), + LastUpdated = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp"), + SessionMemberClientSessionId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Clients", x => x.ClientId); + }); + + migrationBuilder.CreateTable( + name: "SessionMembers", + columns: table => new + { + ClientSessionId = table.Column(type: "uuid", nullable: false), + ClientId = table.Column(type: "uuid", nullable: false), + SessionId = table.Column(type: "uuid", nullable: false), + Created = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp"), + LastUpdated = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp") + }, + constraints: table => + { + table.PrimaryKey("PK_SessionMembers", x => x.ClientSessionId); + table.ForeignKey( + name: "FK_SessionMembers_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "ClientId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Sessions", + columns: table => new + { + SessionId = table.Column(type: "uuid", nullable: false), + OwnerCharacterId = table.Column(type: "text", nullable: false), + OwnerClientId = table.Column(type: "uuid", nullable: false), + ShareCode = table.Column(type: "text", nullable: false), + ReservedConnectionId = table.Column(type: "text", nullable: false), + SharedQuestId = table.Column(type: "integer", nullable: false), + SharedQuestStep = table.Column(type: "smallint", nullable: false), + PartyMembers = table.Column(type: "text", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + SkipPartyCheck = table.Column(type: "boolean", nullable: false), + AllowJoins = table.Column(type: "boolean", nullable: false), + Created = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp"), + LastUpdated = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "current_timestamp"), + SessionMemberClientSessionId = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Sessions", x => x.SessionId); + table.ForeignKey( + name: "FK_Sessions_Clients_OwnerClientId", + column: x => x.OwnerClientId, + principalTable: "Clients", + principalColumn: "ClientId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Sessions_SessionMembers_SessionMemberClientSessionId", + column: x => x.SessionMemberClientSessionId, + principalTable: "SessionMembers", + principalColumn: "ClientSessionId"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Clients_SessionMemberClientSessionId", + table: "Clients", + column: "SessionMemberClientSessionId"); + + migrationBuilder.CreateIndex( + name: "IX_SessionMembers_ClientId", + table: "SessionMembers", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_SessionMembers_SessionId", + table: "SessionMembers", + column: "SessionId"); + + migrationBuilder.CreateIndex( + name: "IX_Sessions_OwnerClientId", + table: "Sessions", + column: "OwnerClientId"); + + migrationBuilder.CreateIndex( + name: "IX_Sessions_SessionMemberClientSessionId", + table: "Sessions", + column: "SessionMemberClientSessionId"); + + migrationBuilder.AddForeignKey( + name: "FK_Clients_SessionMembers_SessionMemberClientSessionId", + table: "Clients", + column: "SessionMemberClientSessionId", + principalTable: "SessionMembers", + principalColumn: "ClientSessionId"); + + migrationBuilder.AddForeignKey( + name: "FK_SessionMembers_Sessions_SessionId", + table: "SessionMembers", + column: "SessionId", + principalTable: "Sessions", + principalColumn: "SessionId", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Clients_SessionMembers_SessionMemberClientSessionId", + table: "Clients"); + + migrationBuilder.DropForeignKey( + name: "FK_Sessions_SessionMembers_SessionMemberClientSessionId", + table: "Sessions"); + + migrationBuilder.DropTable( + name: "Bans"); + + migrationBuilder.DropTable( + name: "SessionMembers"); + + migrationBuilder.DropTable( + name: "Sessions"); + + migrationBuilder.DropTable( + name: "Clients"); + } + } +} diff --git a/QuestShare.Server/Models/SessionMember.cs b/QuestShare.Server/Models/SessionMember.cs new file mode 100644 index 0000000..66f1282 --- /dev/null +++ b/QuestShare.Server/Models/SessionMember.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Newtonsoft.Json; +using QuestShare.Common; + +namespace QuestShare.Server.Models +{ + public class SessionMember + { + public Guid ClientSessionId { get; set; } + public required Client Client { get; set; } + public required Session Session { get; set; } + // public required string CharacterId { get; set; } + public DateTime Created { get; set; } + public DateTime LastUpdated { get; set; } + } + + internal class ClientSessionConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("SessionMembers"); + builder.HasKey(cs => cs.ClientSessionId); + builder.HasMany(); + builder.HasMany(); + builder.Property(cs => cs.Created).HasDefaultValueSql("current_timestamp").ValueGeneratedOnAddOrUpdate(); + builder.Property(cs => cs.LastUpdated).HasDefaultValueSql("current_timestamp").ValueGeneratedOnAddOrUpdate(); + } + } +} diff --git a/QuestShare.Server/Program.cs b/QuestShare.Server/Program.cs index 8c7697d..60b5acd 100644 --- a/QuestShare.Server/Program.cs +++ b/QuestShare.Server/Program.cs @@ -64,11 +64,6 @@ namespace QuestShare.Server } var app = builder.Build(); Log.Information($"Starting QuestShare Server - API Version {Constants.Version}"); - using (var scope = app.Services.CreateScope()) - { - var context = scope.ServiceProvider.GetRequiredService(); - await context.Database.MigrateAsync(); - } app.Run(); } } diff --git a/QuestShare.sln b/QuestShare.sln index 15a2de3..2f6e6de 100644 --- a/QuestShare.sln +++ b/QuestShare.sln @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution plogonmaster.json = plogonmaster.json EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtterGui", "Lib\OtterGui\OtterGui.csproj", "{55C9C0E4-CD7B-4A76-B666-E25A00EA44F7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -31,6 +33,10 @@ Global {7A4BDE00-BF8F-4A09-8363-6434609B6865}.Debug|x64.Build.0 = Debug|Any CPU {7A4BDE00-BF8F-4A09-8363-6434609B6865}.Release|x64.ActiveCfg = Release|Any CPU {7A4BDE00-BF8F-4A09-8363-6434609B6865}.Release|x64.Build.0 = Release|Any CPU + {55C9C0E4-CD7B-4A76-B666-E25A00EA44F7}.Debug|x64.ActiveCfg = Debug|Any CPU + {55C9C0E4-CD7B-4A76-B666-E25A00EA44F7}.Debug|x64.Build.0 = Debug|Any CPU + {55C9C0E4-CD7B-4A76-B666-E25A00EA44F7}.Release|x64.ActiveCfg = Release|Any CPU + {55C9C0E4-CD7B-4A76-B666-E25A00EA44F7}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE