diff --git a/Behavior/Webhook.cs b/Behavior/Webhook.cs index ca2b2ea..658f3bc 100644 --- a/Behavior/Webhook.cs +++ b/Behavior/Webhook.cs @@ -10,6 +10,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using vassago.Models; +using Newtonsoft.Json; [StaticPlz] public class Webhook : Behavior @@ -24,18 +25,13 @@ public class Webhook : Behavior private ConcurrentDictionary authedCache = new ConcurrentDictionary(); private HttpClient hc = new HttpClient(); - public static void SetupWebhooks(IConfigurationSection confSection) + public static void SetupWebhooks(IEnumerable confSection) { - configuredWebhooks = confSection.Get>(); - - foreach (var conf in configuredWebhooks) + //configuredWebhooks = confSection.Get>(); + if(confSection != null) foreach (var confLine in confSection) { + var conf = JsonConvert.DeserializeObject(confLine); var confName = $"Webhook: {conf.Trigger}"; - Console.WriteLine($"confName: {confName}; conf.uri: {conf.Uri}, conf.uacID: {conf.uacID}, conf.Method: {conf.Method}, conf.Headers: {conf.Headers?.Count() ?? 0}, conf.Content: {conf.Content}"); - foreach (var kvp in conf.Headers) - { - Console.WriteLine($"{kvp[0]}: {kvp[1]}"); - } var changed = false; var myUAC = rememberer.SearchUAC(uac => uac.OwnerId == conf.uacID); if (myUAC == null) diff --git a/ConsoleService.cs b/ConsoleService.cs index 97adfd6..c16b644 100644 --- a/ConsoleService.cs +++ b/ConsoleService.cs @@ -7,24 +7,17 @@ namespace vassago using vassago.TwitchInterface; using vassago.ProtocolInterfaces.DiscordInterface; using System.Runtime.CompilerServices; + using Newtonsoft.Json; internal class ConsoleService : BackgroundService { public ConsoleService(IConfiguration aspConfig) { Shared.DBConnectionString = aspConfig["DBConnectionString"]; - Shared.SetupSlashCommands = aspConfig["SetupSlashCommands"]?.ToLower() == "true"; - Shared.API_URL = new Uri(aspConfig["API_URL"]); - DiscordTokens = aspConfig.GetSection("DiscordTokens").Get>(); - TwitchConfigs = aspConfig.GetSection("TwitchConfigs").Get>(); - Conversion.Converter.Load(aspConfig["ExchangePairsLocation"]); - Telefranz.Configure(aspConfig["KafkaName"], aspConfig["KafkaBootstrap"]); - Console.WriteLine($"Telefranz.Configure({aspConfig["KafkaName"]}, {aspConfig["KafkaBootstrap"]});"); - vassago.Behavior.Webhook.SetupWebhooks(aspConfig.GetSection("Webhooks")); } - IEnumerable DiscordTokens { get; } - IEnumerable TwitchConfigs { get; } + List DiscordTokens; + List TwitchConfigs; protected override async Task ExecuteAsync(CancellationToken cancellationToken) { @@ -32,6 +25,14 @@ namespace vassago var dbc = new ChattingContext(); await dbc.Database.MigrateAsync(cancellationToken); + var confEntity = dbc.Configurations.FirstOrDefault() ?? new Configuration(); + if (dbc.Configurations.Count() == 0) + { + dbc.Configurations.Add(confEntity); + dbc.SaveChanges(); + } + dbConfig(ref confEntity); + if (DiscordTokens?.Any() ?? false) foreach (var dt in DiscordTokens) { @@ -47,9 +48,22 @@ namespace vassago initTasks.Add(t.Init(tc)); Shared.ProtocolList.Add(t); } - + Task.WaitAll(initTasks.ToArray(), cancellationToken); } - + private void dbConfig(ref vassago.Models.Configuration confEntity) + { + Shared.SetupSlashCommands = confEntity.SetupDiscordSlashCommands; + Shared.API_URL = new Uri(confEntity.reportedApiUrl); + DiscordTokens = confEntity.DiscordTokens; + TwitchConfigs = new List(); + if(confEntity.TwitchConfigs != null) foreach (var twitchConfString in confEntity.TwitchConfigs) + { + TwitchConfigs.Add(JsonConvert.DeserializeObject(twitchConfString)); + } + Conversion.Converter.Load(confEntity.ExchangePairsLocation); + Telefranz.Configure(confEntity.KafkaName, confEntity.KafkaBootstrap); + vassago.Behavior.Webhook.SetupWebhooks(confEntity.Webhooks); + } } } diff --git a/Migrations/20250628034005_ConfigInDatabase.Designer.cs b/Migrations/20250628034005_ConfigInDatabase.Designer.cs new file mode 100644 index 0000000..958ebba --- /dev/null +++ b/Migrations/20250628034005_ConfigInDatabase.Designer.cs @@ -0,0 +1,427 @@ +// +using System; +using System.Collections.Generic; +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("20250628034005_ConfigInDatabase")] + partial class ConfigInDatabase + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore"); + 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.Configuration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property>("DiscordTokens") + .HasColumnType("text[]"); + + b.Property("ExchangePairsLocation") + .HasColumnType("text"); + + b.Property("KafkaBootstrap") + .HasColumnType("text"); + + b.Property("KafkaName") + .HasColumnType("text"); + + b.Property("SetupDiscordSlashCommands") + .HasColumnType("boolean"); + + b.Property>("TwitchConfigs") + .HasColumnType("text[]"); + + b.Property>("Webhooks") + .HasColumnType("text[]"); + + b.Property("reportedApiUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Configurations"); + }); + + 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.Property("TranslatedContent") + .HasColumnType("text"); + + 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>("CommandAlterations") + .HasColumnType("hstore"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("uuid"); + + b.Property>("Translations") + .HasColumnType("hstore"); + + 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/20250628034005_ConfigInDatabase.cs b/Migrations/20250628034005_ConfigInDatabase.cs new file mode 100644 index 0000000..2500475 --- /dev/null +++ b/Migrations/20250628034005_ConfigInDatabase.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace vassago.Migrations +{ + /// + public partial class ConfigInDatabase : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Configurations", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + DiscordTokens = table.Column>(type: "text[]", nullable: true), + TwitchConfigs = table.Column>(type: "text[]", nullable: true), + ExchangePairsLocation = table.Column(type: "text", nullable: true), + SetupDiscordSlashCommands = table.Column(type: "boolean", nullable: false), + Webhooks = table.Column>(type: "text[]", nullable: true), + KafkaBootstrap = table.Column(type: "text", nullable: true), + KafkaName = table.Column(type: "text", nullable: true), + reportedApiUrl = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Configurations", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Configurations"); + } + } +} diff --git a/Migrations/ChattingContextModelSnapshot.cs b/Migrations/ChattingContextModelSnapshot.cs index df3b658..6a48c4b 100644 --- a/Migrations/ChattingContextModelSnapshot.cs +++ b/Migrations/ChattingContextModelSnapshot.cs @@ -188,6 +188,41 @@ namespace vassago.Migrations b.ToTable("Channels"); }); + modelBuilder.Entity("vassago.Models.Configuration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property>("DiscordTokens") + .HasColumnType("text[]"); + + b.Property("ExchangePairsLocation") + .HasColumnType("text"); + + b.Property("KafkaBootstrap") + .HasColumnType("text"); + + b.Property("KafkaName") + .HasColumnType("text"); + + b.Property("SetupDiscordSlashCommands") + .HasColumnType("boolean"); + + b.Property>("TwitchConfigs") + .HasColumnType("text[]"); + + b.Property>("Webhooks") + .HasColumnType("text[]"); + + b.Property("reportedApiUrl") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Configurations"); + }); + modelBuilder.Entity("vassago.Models.Message", b => { b.Property("Id") diff --git a/Models/ChattingContext.cs b/Models/ChattingContext.cs index b7e4478..26919f4 100644 --- a/Models/ChattingContext.cs +++ b/Models/ChattingContext.cs @@ -11,6 +11,7 @@ public class ChattingContext : DbContext public DbSet Messages { get; set; } public DbSet Accounts { get; set; } public DbSet Users { get; set; } + public DbSet Configurations {get; set;} public ChattingContext(DbContextOptions options) : base(options) { } public ChattingContext() : base() { } diff --git a/Models/Configuration.cs b/Models/Configuration.cs new file mode 100644 index 0000000..8c52a1d --- /dev/null +++ b/Models/Configuration.cs @@ -0,0 +1,28 @@ +namespace vassago.Models; + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; +using System.Threading.Tasks; +using Discord.WebSocket; +using Microsoft.EntityFrameworkCore; +using vassago.TwitchInterface; +using vassago.Behavior; + +//TODO: it feels gross to have a *table* in a database that's intended to hold 1 **UND EXACTLY ONE** row, ever. +//but also it feels worse to scatter my configuraiton-y data across external files and the database. + +public class Configuration +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + public List DiscordTokens { get; set; } + public List TwitchConfigs { get; set; } + public string ExchangePairsLocation { get; set; } = "assets/exchangepairs.json"; //TODO: have this be "exchange API key", so you can have it continually update. + public bool SetupDiscordSlashCommands { get; set; } = false; //i'm kind of idealogically opposed to these. + public List Webhooks { get; set; } + public string KafkaBootstrap { get; set; } = "http://localhost:9092"; + public string KafkaName { get; set; } = "vassago"; + public string reportedApiUrl { get; set; } = "http://localhost:5093/api"; +} diff --git a/Rememberer.cs b/Rememberer.cs index 7e8dc8b..b890a4c 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -347,4 +347,11 @@ public class Rememberer if (toRemember.Channels?.Count() > 0) cacheChannels(); } + public Configuration Configuration() + { + dbAccessSemaphore.Wait(); + var toReturn = db.Configurations.FirstOrDefault(); + dbAccessSemaphore.Release(); + return toReturn; + } } diff --git a/WebInterface/Controllers/ConfigurationController.cs b/WebInterface/Controllers/ConfigurationController.cs new file mode 100644 index 0000000..dc3a259 --- /dev/null +++ b/WebInterface/Controllers/ConfigurationController.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using System.Text; +using Newtonsoft.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using vassago; +using vassago.Behavior; +using vassago.Models; +using vassago.WebInterface.Models; + +namespace vassago.WebInterface.Controllers; + +public class ConfigurationController() : Controller +{ + private static Rememberer r = Rememberer.Instance; + public IActionResult Index() + { + var conf = r.Configuration() ?? new Configuration(); + ViewData.Add("Serialized", JsonConvert.SerializeObject(conf)); + return View(conf); + } + + [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/HomeController.cs b/WebInterface/Controllers/HomeController.cs index d9ebaf1..19395f1 100644 --- a/WebInterface/Controllers/HomeController.cs +++ b/WebInterface/Controllers/HomeController.cs @@ -27,6 +27,8 @@ public class HomeController : Controller var sb = new StringBuilder(); sb.Append('['); + sb.Append($"{{\"text\": \"Configuration\"}},"); + //UACs var allUACs = r.UACsOverview(); var first = true; @@ -81,7 +83,6 @@ public class HomeController : Controller sb.Append("]}"); } - //type error, e is not defined //channels sb.Append(",{text: \"channels\", expanded:true, nodes: ["); var topLevelChannels = r.ChannelsOverview().Where(x => x.ParentChannel == null).ToList(); diff --git a/WebInterface/Views/Configuration/Index.cshtml b/WebInterface/Views/Configuration/Index.cshtml new file mode 100644 index 0000000..12751be --- /dev/null +++ b/WebInterface/Views/Configuration/Index.cshtml @@ -0,0 +1,65 @@ +@model vassago.Models.Configuration +@{ +} +home/configuration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Discord
+
Twitch
Webhooks
+ + +@section Scripts{ + +}