diff --git a/Behavior/Behavior.cs b/Behavior/Behavior.cs index 0b27dda..443908f 100644 --- a/Behavior/Behavior.cs +++ b/Behavior/Behavior.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; public abstract class Behavior { + //recommendation: set up your UACs in your constructor. public abstract Task ActOn(Message message); public virtual bool ShouldAct(Message message) diff --git a/Behavior/TwitchSummon.cs b/Behavior/TwitchSummon.cs index e726065..ec0882b 100644 --- a/Behavior/TwitchSummon.cs +++ b/Behavior/TwitchSummon.cs @@ -11,6 +11,37 @@ public class TwitchSummon : Behavior public override string Trigger => "!twitchsummon"; + private static Guid uacID = new Guid("06ad2008-3d48-4ba6-8722-7eaea000ec70"); + private static UAC myUAC; + + public TwitchSummon() + { + myUAC = Rememberer.SearchUAC(uac => uac.OwnerId == uacID); + if(myUAC == null) + { + myUAC = new() + { + OwnerId = uacID, + DisplayName = Name + }; + Rememberer.RememberUAC(myUAC); + } + } + public override bool ShouldAct(Message message) + { + if (!base.ShouldAct(message)) + return false; + var uacConf = Rememberer.SearchUAC(uac => uac.OwnerId == uacID); + if (uacConf == null) + { + Console.Error.WriteLine("no UAC conf for TwitchSummon! Set one up!"); + } + + Console.WriteLine($"uacConf: {uacConf} users: {uacConf?.Users?.Count()}. message author: {message?.Author}. has an IsUser: {message?.Author?.IsUser}."); + Console.WriteLine($"and therefore: {uacConf.Users.Contains(message.Author.IsUser)}"); + return uacConf.Users.Contains(message.Author.IsUser); + } + public override async Task ActOn(Message message) { var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault(); diff --git a/Migrations/20250423002254_UAC.Designer.cs b/Migrations/20250423002254_UAC.Designer.cs new file mode 100644 index 0000000..a3a2eba --- /dev/null +++ b/Migrations/20250423002254_UAC.Designer.cs @@ -0,0 +1,378 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using vassago.Models; + +#nullable disable + +namespace vassago.Migrations +{ + [DbContext(typeof(ChattingContext))] + [Migration("20250423002254_UAC")] + partial class UAC + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AccountUAC", b => + { + b.Property("AccountInChannelsId") + .HasColumnType("uuid"); + + b.Property("UACsId") + .HasColumnType("uuid"); + + b.HasKey("AccountInChannelsId", "UACsId"); + + b.HasIndex("UACsId"); + + b.ToTable("AccountUAC"); + }); + + modelBuilder.Entity("ChannelUAC", b => + { + b.Property("ChannelsId") + .HasColumnType("uuid"); + + b.Property("UACsId") + .HasColumnType("uuid"); + + b.HasKey("ChannelsId", "UACsId"); + + b.HasIndex("UACsId"); + + b.ToTable("ChannelUAC"); + }); + + modelBuilder.Entity("UACUser", b => + { + b.Property("UACsId") + .HasColumnType("uuid"); + + b.Property("UsersId") + .HasColumnType("uuid"); + + b.HasKey("UACsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("UACUser"); + }); + + modelBuilder.Entity("vassago.Models.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("IsBot") + .HasColumnType("boolean"); + + b.Property("IsUserId") + .HasColumnType("uuid"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("SeenInChannelId") + .HasColumnType("uuid"); + + b.Property("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("IsUserId"); + + b.HasIndex("SeenInChannelId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("vassago.Models.Attachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Content") + .HasColumnType("bytea"); + + b.Property("ContentType") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("numeric(20,0)"); + + b.Property("Filename") + .HasColumnType("text"); + + b.Property("MessageId") + .HasColumnType("uuid"); + + b.Property("Size") + .HasColumnType("integer"); + + b.Property("Source") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.ToTable("Attachments"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelType") + .HasColumnType("integer"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("LewdnessFilterLevel") + .HasColumnType("integer"); + + b.Property("LinksAllowed") + .HasColumnType("boolean"); + + b.Property("MaxAttachmentBytes") + .HasColumnType("numeric(20,0)"); + + b.Property("MaxTextChars") + .HasColumnType("bigint"); + + b.Property("MeannessFilterLevel") + .HasColumnType("integer"); + + b.Property("ParentChannelId") + .HasColumnType("uuid"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("ReactionsPossible") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("ParentChannelId"); + + b.ToTable("Channels"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActedOn") + .HasColumnType("boolean"); + + b.Property("AuthorId") + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Content") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasColumnType("text"); + + b.Property("MentionsMe") + .HasColumnType("boolean"); + + b.Property("Protocol") + .HasColumnType("text"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("ChannelId"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("vassago.Models.UAC", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("UACs"); + }); + + modelBuilder.Entity("vassago.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("AccountUAC", b => + { + b.HasOne("vassago.Models.Account", null) + .WithMany() + .HasForeignKey("AccountInChannelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChannelUAC", b => + { + b.HasOne("vassago.Models.Channel", null) + .WithMany() + .HasForeignKey("ChannelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("UACUser", b => + { + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("vassago.Models.Account", b => + { + b.HasOne("vassago.Models.User", "IsUser") + .WithMany("Accounts") + .HasForeignKey("IsUserId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("vassago.Models.Channel", "SeenInChannel") + .WithMany("Users") + .HasForeignKey("SeenInChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("IsUser"); + + b.Navigation("SeenInChannel"); + }); + + modelBuilder.Entity("vassago.Models.Attachment", b => + { + b.HasOne("vassago.Models.Message", "Message") + .WithMany("Attachments") + .HasForeignKey("MessageId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Message"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.HasOne("vassago.Models.Channel", "ParentChannel") + .WithMany("SubChannels") + .HasForeignKey("ParentChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("ParentChannel"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.HasOne("vassago.Models.Account", "Author") + .WithMany() + .HasForeignKey("AuthorId"); + + b.HasOne("vassago.Models.Channel", "Channel") + .WithMany("Messages") + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Author"); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("vassago.Models.Channel", b => + { + b.Navigation("Messages"); + + b.Navigation("SubChannels"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("vassago.Models.Message", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("vassago.Models.User", b => + { + b.Navigation("Accounts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20250423002254_UAC.cs b/Migrations/20250423002254_UAC.cs new file mode 100644 index 0000000..46ade2b --- /dev/null +++ b/Migrations/20250423002254_UAC.cs @@ -0,0 +1,131 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace vassago.Migrations +{ + /// + public partial class UAC : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UACs", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OwnerId = table.Column(type: "uuid", nullable: false), + DisplayName = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UACs", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AccountUAC", + columns: table => new + { + AccountInChannelsId = table.Column(type: "uuid", nullable: false), + UACsId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AccountUAC", x => new { x.AccountInChannelsId, x.UACsId }); + table.ForeignKey( + name: "FK_AccountUAC_Accounts_AccountInChannelsId", + column: x => x.AccountInChannelsId, + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AccountUAC_UACs_UACsId", + column: x => x.UACsId, + principalTable: "UACs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ChannelUAC", + columns: table => new + { + ChannelsId = table.Column(type: "uuid", nullable: false), + UACsId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChannelUAC", x => new { x.ChannelsId, x.UACsId }); + table.ForeignKey( + name: "FK_ChannelUAC_Channels_ChannelsId", + column: x => x.ChannelsId, + principalTable: "Channels", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ChannelUAC_UACs_UACsId", + column: x => x.UACsId, + principalTable: "UACs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UACUser", + columns: table => new + { + UACsId = table.Column(type: "uuid", nullable: false), + UsersId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UACUser", x => new { x.UACsId, x.UsersId }); + table.ForeignKey( + name: "FK_UACUser_UACs_UACsId", + column: x => x.UACsId, + principalTable: "UACs", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UACUser_Users_UsersId", + column: x => x.UsersId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AccountUAC_UACsId", + table: "AccountUAC", + column: "UACsId"); + + migrationBuilder.CreateIndex( + name: "IX_ChannelUAC_UACsId", + table: "ChannelUAC", + column: "UACsId"); + + migrationBuilder.CreateIndex( + name: "IX_UACUser_UsersId", + table: "UACUser", + column: "UsersId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AccountUAC"); + + migrationBuilder.DropTable( + name: "ChannelUAC"); + + migrationBuilder.DropTable( + name: "UACUser"); + + migrationBuilder.DropTable( + name: "UACs"); + } + } +} diff --git a/Migrations/ChattingContextModelSnapshot.cs b/Migrations/ChattingContextModelSnapshot.cs index 06ae3d3..15593f0 100644 --- a/Migrations/ChattingContextModelSnapshot.cs +++ b/Migrations/ChattingContextModelSnapshot.cs @@ -22,6 +22,51 @@ namespace vassago.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("AccountUAC", b => + { + b.Property("AccountInChannelsId") + .HasColumnType("uuid"); + + b.Property("UACsId") + .HasColumnType("uuid"); + + b.HasKey("AccountInChannelsId", "UACsId"); + + b.HasIndex("UACsId"); + + b.ToTable("AccountUAC"); + }); + + modelBuilder.Entity("ChannelUAC", b => + { + b.Property("ChannelsId") + .HasColumnType("uuid"); + + b.Property("UACsId") + .HasColumnType("uuid"); + + b.HasKey("ChannelsId", "UACsId"); + + b.HasIndex("UACsId"); + + b.ToTable("ChannelUAC"); + }); + + modelBuilder.Entity("UACUser", b => + { + b.Property("UACsId") + .HasColumnType("uuid"); + + b.Property("UsersId") + .HasColumnType("uuid"); + + b.HasKey("UACsId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("UACUser"); + }); + modelBuilder.Entity("vassago.Models.Account", b => { b.Property("Id") @@ -180,6 +225,23 @@ namespace vassago.Migrations b.ToTable("Messages"); }); + modelBuilder.Entity("vassago.Models.UAC", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("UACs"); + }); + modelBuilder.Entity("vassago.Models.User", b => { b.Property("Id") @@ -191,6 +253,51 @@ namespace vassago.Migrations b.ToTable("Users"); }); + modelBuilder.Entity("AccountUAC", b => + { + b.HasOne("vassago.Models.Account", null) + .WithMany() + .HasForeignKey("AccountInChannelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChannelUAC", b => + { + b.HasOne("vassago.Models.Channel", null) + .WithMany() + .HasForeignKey("ChannelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("UACUser", b => + { + b.HasOne("vassago.Models.UAC", null) + .WithMany() + .HasForeignKey("UACsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("vassago.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("vassago.Models.Account", b => { b.HasOne("vassago.Models.User", "IsUser") diff --git a/Models/Account.cs b/Models/Account.cs index 7ca9aba..3d2dd81 100644 --- a/Models/Account.cs +++ b/Models/Account.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Reflection; using System.Text.Json.Serialization; -using System.Threading.Tasks; public class Account { @@ -28,6 +27,7 @@ public class Account public bool IsBot { get; set; } //webhook counts public Channel SeenInChannel { get; set; } public string Protocol { get; set; } + public List UACs { get; set; } [JsonIgnore] public User IsUser {get; set;} -} \ No newline at end of file +} diff --git a/Models/Channel.cs b/Models/Channel.cs index 0026965..d51f7dc 100644 --- a/Models/Channel.cs +++ b/Models/Channel.cs @@ -35,6 +35,8 @@ public class Channel public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; } public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; } + public List UACs { get; set; } + [NonSerialized] public Func SendFile; @@ -111,4 +113,4 @@ public class DefinitePermissionSettings public bool ReactionsPossible { get; set; } public Enumerations.LewdnessFilterLevel LewdnessFilterLevel { get; set; } public Enumerations.MeannessFilterLevel MeannessFilterLevel { get; set; } -} \ No newline at end of file +} diff --git a/Models/ChattingContext.cs b/Models/ChattingContext.cs index 43490e2..b7e4478 100644 --- a/Models/ChattingContext.cs +++ b/Models/ChattingContext.cs @@ -7,7 +7,7 @@ public class ChattingContext : DbContext { public DbSet Attachments { get; set; } public DbSet Channels { get; set; } - //public DbSet Emoji {get;set;} + public DbSet UACs { get; set; } public DbSet Messages { get; set; } public DbSet Accounts { get; set; } public DbSet Users { get; set; } @@ -19,4 +19,4 @@ public class ChattingContext : DbContext optionsBuilder.UseNpgsql(Shared.DBConnectionString); //.EnableSensitiveDataLogging(true); //"sensitive" is one thing. writing "did something" every time you think a thought is a different thing. } -} \ No newline at end of file +} diff --git a/Models/UAC.cs b/Models/UAC.cs new file mode 100644 index 0000000..63f1469 --- /dev/null +++ b/Models/UAC.cs @@ -0,0 +1,23 @@ +namespace vassago.Models; + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using vassago.Models; + +public class UAC +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + /// + ///behaviors will have + ///a hardcoded ID thing + ///so they can find theirs. + /// + public Guid OwnerId { get; set;} + public string DisplayName { get; set; } + public List AccountInChannels { get; set; } + public List Channels { get; set; } + public List Users { get; set; } +} diff --git a/Models/User.cs b/Models/User.cs index 9184a4f..8bf0626 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -13,6 +13,8 @@ public class User [DeleteBehavior(DeleteBehavior.Cascade)] public List Accounts { get; set; } + public List UACs { get; set; } + //if I ever get lots and lots of tags, or some automatic way to register a feature's arbitrary tags, then I can move this off. //public bool Tag_CanTwitchSummon { get; set; } @@ -30,8 +32,8 @@ public class User } else { - return $"[accountless {Id}"; + return $"[accountless {Id}]"; } } } -} \ No newline at end of file +} diff --git a/Rememberer.cs b/Rememberer.cs index 3cfca8a..db82a43 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -162,4 +162,28 @@ public static class Rememberer dbAccessSemaphore.Release(); return toReturn; } + public static List UACsOverview() + { + List toReturn; + dbAccessSemaphore.Wait(); + toReturn = db.UACs.Include(uac => uac.Users).Include(uac => uac.Channels).Include(uac => uac.AccountInChannels).ToList(); + dbAccessSemaphore.Release(); + return toReturn; + } + public static UAC SearchUAC(Expression> predicate) + { + UAC toReturn; + dbAccessSemaphore.Wait(); + toReturn = db.UACs.Include(uac => uac.Users).Include(uac => uac.Channels).Include(uac => uac.AccountInChannels) + .FirstOrDefault(predicate); + dbAccessSemaphore.Release(); + return toReturn; + } + public static void RememberUAC(UAC toRemember) + { + dbAccessSemaphore.Wait(); + db.Update(toRemember); + db.SaveChanges(); + dbAccessSemaphore.Release(); + } } diff --git a/WebInterface/Controllers/HomeController.cs b/WebInterface/Controllers/HomeController.cs index f0d9b00..0c3ca2a 100644 --- a/WebInterface/Controllers/HomeController.cs +++ b/WebInterface/Controllers/HomeController.cs @@ -25,10 +25,61 @@ public class HomeController : Controller Console.WriteLine($"accounts: {allAccounts?.Count ?? 0}, channels: {allChannels?.Count ?? 0}"); var sb = new StringBuilder(); sb.Append('['); - sb.Append("{text: \"channels\", expanded:true, nodes: ["); + //UACs + var allUACs = Rememberer.UACsOverview(); var first = true; + if(allUACs.Any()) + { + sb.Append("{text: \"uacs\", expanded:true, nodes: ["); + first=true; + foreach(var uac in allUACs) + { + if (first) + { + first = false; + } + else + { + sb.Append(','); + } + sb.Append($"{{\"text\": \"{uac.DisplayName}\"}}"); + } + sb.Append("]}"); + } + else + { + sb.Append("{text: \"uacs (0)\", }"); + } + + //users + var users = Rememberer.UsersOverview(); + if(users.Any()) + { + sb.Append(",{text: \"users\", expanded:true, nodes: ["); + first=true; + //refresh list; we'll be knocking them out again in serializeUser + allAccounts = Rememberer.AccountsOverview(); + foreach(var user in users) + { + if (first) + { + first = false; + } + else + { + sb.Append(','); + } + serializeUser(ref sb, ref allAccounts, user); + } + sb.Append("]}"); + } + + //type error, e is not defined + //channels + sb.Append(",{text: \"channels\", expanded:true, nodes: ["); var topLevelChannels = Rememberer.ChannelsOverview().Where(x => x.ParentChannel == null); + first = true; foreach (var topLevelChannel in topLevelChannels) { if (first) @@ -84,28 +135,8 @@ public class HomeController : Controller } sb.Append("]}"); } - var users = Rememberer.UsersOverview();// _db.Users.ToList(); - if(users.Any()) - { - sb.Append(",{text: \"users\", expanded:true, nodes: ["); - first=true; - //refresh list; we'll be knocking them out again in serializeUser - allAccounts = Rememberer.AccountsOverview(); - foreach(var user in users) - { - if (first) - { - first = false; - } - else - { - sb.Append(','); - } - serializeUser(ref sb, ref allAccounts, user); - } - sb.Append("]}"); - } - sb.Append(']'); + + sb.Append("]"); ViewData.Add("treeString", sb.ToString()); return View("Index"); } @@ -121,7 +152,6 @@ public class HomeController : Controller if (currentChannel.SubChannels != null || theseAccounts != null) { sb.Append(", \"nodes\": ["); - } if (currentChannel.SubChannels != null) { foreach (var subChannel in currentChannel.SubChannels) @@ -159,7 +189,9 @@ public class HomeController : Controller } sb.Append("]}"); } - sb.Append("]}"); + sb.Append(']'); + } + sb.Append('}'); } private void serializeAccount(ref StringBuilder sb, Account currentAccount) { @@ -171,17 +203,21 @@ public class HomeController : Controller sb.Append(currentUser.DisplayName); sb.Append("\", "); var ownedAccounts = allAccounts.Where(a => a.IsUser == currentUser); - sb.Append("nodes: ["); - sb.Append($"{{\"text\": \"owned accounts:\", \"expanded\":true, \"nodes\": ["); - if (ownedAccounts != null) + if (ownedAccounts?.Count() > 0) { + sb.Append("nodes: ["); + sb.Append($"{{\"text\": \"owned accounts:\", \"expanded\":true, \"nodes\": ["); + var first = true; foreach (var acc in ownedAccounts) { + if(!first) + sb.Append(','); serializeAccount(ref sb, acc); - sb.Append(','); + first = false; } + sb.Append("]}]"); } - sb.Append("]}]}"); + sb.Append("}"); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] diff --git a/WebInterface/Controllers/UACsController.cs b/WebInterface/Controllers/UACsController.cs new file mode 100644 index 0000000..40ba7ba --- /dev/null +++ b/WebInterface/Controllers/UACsController.cs @@ -0,0 +1,25 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using vassago.Models; +using vassago.WebInterface.Models; + +namespace vassago.WebInterface.Controllers; + +public class UACsController() : Controller +{ + public IActionResult Index() + { + return View(Rememberer.UACsOverview()); + } + public IActionResult Details(Guid id) + { + return View(Rememberer.SearchUAC(uac => uac.Id == id)); + } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } +} diff --git a/WebInterface/Controllers/UsersController.cs b/WebInterface/Controllers/UsersController.cs index f18ba0a..5ed9592 100644 --- a/WebInterface/Controllers/UsersController.cs +++ b/WebInterface/Controllers/UsersController.cs @@ -36,4 +36,4 @@ public class UsersController(ChattingContext db) : Controller { return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } -} \ No newline at end of file +} diff --git a/WebInterface/Controllers/api/UAC.cs b/WebInterface/Controllers/api/UAC.cs new file mode 100644 index 0000000..291b302 --- /dev/null +++ b/WebInterface/Controllers/api/UAC.cs @@ -0,0 +1,126 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using vassago.Models; +using vassago.ProtocolInterfaces.DiscordInterface; + +namespace vassago.Controllers.api; + +[Route("api/[controller]")] +[ApiController] +public class UACController: ControllerBase +{ + private readonly ILogger _logger; + + public UACController(ILogger logger) + { + _logger = logger; + } + + //microsoft: "you can't have multiple [FromBody]. The reason for this rule is some bullshti about storage buffers." + //cool story, bro. nobody gives a fuck, look at the boilerplate you've necessitated. + public class extraSpecialObjectReadGlorifiedTupleFor_LinkChannel + { + public Guid uac_guid; + public Guid channel_guid; + } + [HttpPatch] + [Route("LinkChannel")] + [Produces("application/json")] + public IActionResult LinkChannel([FromBody] extraSpecialObjectReadGlorifiedTupleFor_LinkChannel req) + { + var uac_guid = req.uac_guid; + var channel_guid = req.channel_guid; + var uacFromDb = Rememberer.SearchUAC(uac => uac.Id == uac_guid); + if (uacFromDb == null) + { + var err =$"attempt to link channel for uac {uac_guid}, not found"; + _logger.LogError(err); + return NotFound(err); + } + var channelFromDb = Rememberer.SearchChannel(c => c.Id == channel_guid); + if (channelFromDb == null) + { + var err = $"attempt to link channel for channel {channel_guid}, not found"; + _logger.LogError(err); + return NotFound(err); + } + + uacFromDb.Channels ??= []; + if (uacFromDb.Channels.Contains(channelFromDb)) + { + return BadRequest("channel already linked"); + } + uacFromDb.Channels.Add(channelFromDb); + Rememberer.RememberUAC(uacFromDb); + return Ok(uacFromDb); + } + public class extraSpecialObjectReadGlorifiedTupleFor_LinkUser + { + public Guid uac_guid; + public Guid user_guid; + } + [HttpPatch] + [Route("LinkUser")] + [Produces("application/json")] + public IActionResult LinkUser([FromBody] extraSpecialObjectReadGlorifiedTupleFor_LinkUser req) + { + var uac_guid = req.uac_guid; + var user_guid = req.user_guid; + var uacFromDb = Rememberer.SearchUAC(uac => uac.Id == uac_guid); + if (uacFromDb == null) + { + _logger.LogError($"attempt to link channal for uac {uac_guid}, not found"); + return NotFound(); + } + var userFromDb = Rememberer.SearchUser(c => c.Id == user_guid); + if (userFromDb == null) + { + _logger.LogError($"attempt to link user for user {user_guid}, not found"); + return NotFound(); + } + + uacFromDb.Users ??= []; + if (uacFromDb.Users.Contains(userFromDb)) + { + return BadRequest("user already linked"); + } + uacFromDb.Users.Add(userFromDb); + Rememberer.RememberUAC(uacFromDb); + return Ok(uacFromDb); + } + public class extraSpecialObjectReadGlorifiedTupleFor_LinkAccount + { + public Guid uac_guid; + public Guid account_guid; + } + [HttpPatch] + [Route("LinkAccount")] + [Produces("application/json")] + public IActionResult LinkAccount([FromBody] extraSpecialObjectReadGlorifiedTupleFor_LinkAccount req) + { + var uac_guid = req.uac_guid; + var account_guid = req.account_guid; + var uacFromDb = Rememberer.SearchUAC(uac => uac.Id == uac_guid); + if (uacFromDb == null) + { + _logger.LogError($"attempt to link channal for uac {uac_guid}, not found"); + return NotFound(); + } + var accountFromDb = Rememberer.SearchAccount(c => c.Id == account_guid); + if (accountFromDb == null) + { + _logger.LogError($"attempt to link account for user {account_guid}, not found"); + return NotFound(); + } + + uacFromDb.AccountInChannels ??= []; + if (uacFromDb.AccountInChannels.Contains(accountFromDb)) + { + return BadRequest("account already linked"); + } + uacFromDb.AccountInChannels.Add(accountFromDb); + Rememberer.RememberUAC(uacFromDb); + return Ok(uacFromDb); + } +} diff --git a/WebInterface/Views/Home/Index.cshtml b/WebInterface/Views/Home/Index.cshtml index 4f5544e..be139ce 100644 --- a/WebInterface/Views/Home/Index.cshtml +++ b/WebInterface/Views/Home/Index.cshtml @@ -7,10 +7,11 @@ -} \ No newline at end of file +} diff --git a/WebInterface/Views/UACs/Details.cshtml b/WebInterface/Views/UACs/Details.cshtml new file mode 100644 index 0000000..3a662b0 --- /dev/null +++ b/WebInterface/Views/UACs/Details.cshtml @@ -0,0 +1,151 @@ +@using System.ComponentModel +@using Newtonsoft.Json +@using System.Text; +@model UAC + +home/ +@Html.Raw(ViewData["breadcrumbs"]) + + + + + + + + + + + + + + + + + + + + +
Display Name@Model.DisplayName
Channels + @Html.Raw("
") +
Users + @Html.Raw("
") +
AccountInChannels + @Html.Raw("
") +
+ + + +@section Scripts{ + +} diff --git a/WebInterface/Views/Users/Details.cshtml b/WebInterface/Views/Users/Details.cshtml index cfd2741..86a5a5d 100644 --- a/WebInterface/Views/Users/Details.cshtml +++ b/WebInterface/Views/Users/Details.cshtml @@ -19,13 +19,7 @@
- - Permission Tags - -
- - - + @section Scripts{ @@ -65,26 +59,7 @@ return tree; } - function getTagsTree() { - @{ - sb = new StringBuilder(); - sb.Append("[{text: \"permission tags\", \"expanded\":true, nodes: ["); - first = true; - for (int i = 0; i < 1; i++) - { - if (!first) - sb.Append(','); - sb.Append($"{{text: \" is goated (w/ sauce)\"}}"); - first = false; - } - sb.Append("]}]"); - } - console.log(@Html.Raw(sb.ToString())); - var tree = @Html.Raw(sb.ToString()); - return tree; - } $('#accountsTree').bstreeview({ data: getAccountsTree() }); - $('#tagsTree').bstreeview({ data: getTagsTree() }); document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } }); } diff --git a/devuitls.sh b/devutils.sh similarity index 100% rename from devuitls.sh rename to devutils.sh diff --git a/wwwroot/js/site.js b/wwwroot/js/site.js index 06de7e7..ac259c4 100644 --- a/wwwroot/js/site.js +++ b/wwwroot/js/site.js @@ -1,7 +1,5 @@ -// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification -// for details on configuring this project to bundle and minify static web assets. +var apiUrl = '/api/'; -// Write your JavaScript code. function Account(displayName, accountId, protocol){ this.displayName = displayName; this.accountId = accountId; @@ -10,23 +8,22 @@ function Account(displayName, accountId, protocol){ //todo: figure out what the URL actually needs to be, rather than assuming you get a whole-ass server to yourself. //you selfish fuck... What are you, fox? //as it stands, you want something like /api/Channels/, trailing slash intentional -function patchModel(model, apiUrl) +function patchModel(model, deprecated_apiUrl) { //structure the model your (dang) self into a nice object console.log(model); - //i know the page url. console.log(window.location.pathname); var components = window.location.pathname.split('/'); - if(components[2] !== "Details") - { - console.log("wtf are you doing? " + components[2] + " is something other than Details"); - } + // if(components[2] !== "Details") + // { + // console.log("wtf are you doing? " + components[2] + " is something other than Details"); + // } var type=components[1]; - var id=components[3]; + // var id=components[3]; console.log("dexter impression: I am now ready to post the following content:"); console.log(JSON.stringify(model)); - fetch(apiUrl, { + fetch(apiUrl + type + '/', { method: 'PATCH', headers: { 'Content-Type': 'application/json', @@ -48,16 +45,16 @@ function patchModel(model, apiUrl) }); } -function deleteModel(model, apiUrl) +function deleteModel(model, deprecated_apiUrl) { var components = window.location.pathname.split('/'); - if(components[2] !== "Details") - { - console.log("wtf are you doing? " + components[2] + " is something other than Details"); - } + // if(components[2] !== "Details") + // { + // console.log("wtf are you doing? " + components[2] + " is something other than Details"); + // } var type=components[1]; - var id=components[3]; - fetch(apiUrl, { + // var id=components[3]; + fetch(apiUrl + type + '/', { method: 'DELETE', headers: { 'Content-Type': 'application/json', @@ -76,5 +73,86 @@ function deleteModel(model, apiUrl) }) .catch(error => { console.error('Error:', error); - }); -} \ No newline at end of file + }); +} +function linkUAC_Channel(channel_guid) +{ + var components = window.location.pathname.split('/'); + var id=components[3]; + let model={"uac_guid": id, + "channel_guid": channel_guid}; + fetch(apiUrl + "UAC/LinkChannel/", { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(model), + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not "ok". which is not ok.'); + } + return response.json(); + }) + .then(returnedSuccessdata => { + // perhaps a success callback + console.log('returnedSuccessdata:', returnedSuccessdata); + }) + .catch(error => { + console.error('Error:', error); + }); +} +function linkUAC_User(user_guid) +{ + var components = window.location.pathname.split('/'); + var id=components[3]; + let model={"uac_guid": id, + "user_guid": user_guid}; + fetch(apiUrl + "UAC/LinkUser/", { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(model), + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not "ok". which is not ok.'); + } + return response.json(); + }) + .then(returnedSuccessdata => { + // perhaps a success callback + console.log('returnedSuccessdata:', returnedSuccessdata); + }) + .catch(error => { + console.error('Error:', error); + }); +} +function linkUAC_Account(account_guid) +{ + var components = window.location.pathname.split('/'); + var id=components[3]; + let model={"uac_guid": id, + "account_guid": account_guid}; + fetch(apiUrl + "UAC/LinkAccount/", { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(model), + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not "ok". which is not ok.'); + } + return response.json(); + }) + .then(returnedSuccessdata => { + // perhaps a success callback + console.log('returnedSuccessdata:', returnedSuccessdata); + }) + .catch(error => { + console.error('Error:', error); + }); +}