UACs, have a web interface, can be linked
All checks were successful
gitea.arg.rip/vassago/pipeline/head This commit looks good

twitch interface needs to collapse users, it's creating redundant copies
UACs need to be un-linkable
no search and no list make homer something something
This commit is contained in:
adam 2025-04-23 11:44:11 -04:00
parent 8b41696e00
commit be94366763
20 changed files with 1177 additions and 86 deletions

View File

@ -10,6 +10,7 @@ using System.Collections.Generic;
public abstract class Behavior public abstract class Behavior
{ {
//recommendation: set up your UACs in your constructor.
public abstract Task<bool> ActOn(Message message); public abstract Task<bool> ActOn(Message message);
public virtual bool ShouldAct(Message message) public virtual bool ShouldAct(Message message)

View File

@ -11,6 +11,37 @@ public class TwitchSummon : Behavior
public override string Trigger => "!twitchsummon"; 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<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault(); var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault();

378
Migrations/20250423002254_UAC.Designer.cs generated Normal file
View File

@ -0,0 +1,378 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("AccountInChannelsId")
.HasColumnType("uuid");
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.HasKey("AccountInChannelsId", "UACsId");
b.HasIndex("UACsId");
b.ToTable("AccountUAC");
});
modelBuilder.Entity("ChannelUAC", b =>
{
b.Property<Guid>("ChannelsId")
.HasColumnType("uuid");
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.HasKey("ChannelsId", "UACsId");
b.HasIndex("UACsId");
b.ToTable("ChannelUAC");
});
modelBuilder.Entity("UACUser", b =>
{
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.Property<Guid>("UsersId")
.HasColumnType("uuid");
b.HasKey("UACsId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("UACUser");
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("DisplayName")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<bool>("IsBot")
.HasColumnType("boolean");
b.Property<Guid?>("IsUserId")
.HasColumnType("uuid");
b.Property<string>("Protocol")
.HasColumnType("text");
b.Property<Guid?>("SeenInChannelId")
.HasColumnType("uuid");
b.Property<string>("Username")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("IsUserId");
b.HasIndex("SeenInChannelId");
b.ToTable("Accounts");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<byte[]>("Content")
.HasColumnType("bytea");
b.Property<string>("ContentType")
.HasColumnType("text");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<decimal?>("ExternalId")
.HasColumnType("numeric(20,0)");
b.Property<string>("Filename")
.HasColumnType("text");
b.Property<Guid?>("MessageId")
.HasColumnType("uuid");
b.Property<int>("Size")
.HasColumnType("integer");
b.Property<string>("Source")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("MessageId");
b.ToTable("Attachments");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int>("ChannelType")
.HasColumnType("integer");
b.Property<string>("DisplayName")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<int?>("LewdnessFilterLevel")
.HasColumnType("integer");
b.Property<bool?>("LinksAllowed")
.HasColumnType("boolean");
b.Property<decimal?>("MaxAttachmentBytes")
.HasColumnType("numeric(20,0)");
b.Property<long?>("MaxTextChars")
.HasColumnType("bigint");
b.Property<int?>("MeannessFilterLevel")
.HasColumnType("integer");
b.Property<Guid?>("ParentChannelId")
.HasColumnType("uuid");
b.Property<string>("Protocol")
.HasColumnType("text");
b.Property<bool?>("ReactionsPossible")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("ParentChannelId");
b.ToTable("Channels");
});
modelBuilder.Entity("vassago.Models.Message", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("ActedOn")
.HasColumnType("boolean");
b.Property<Guid?>("AuthorId")
.HasColumnType("uuid");
b.Property<Guid?>("ChannelId")
.HasColumnType("uuid");
b.Property<string>("Content")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<bool>("MentionsMe")
.HasColumnType("boolean");
b.Property<string>("Protocol")
.HasColumnType("text");
b.Property<DateTimeOffset>("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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("DisplayName")
.HasColumnType("text");
b.Property<Guid>("OwnerId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("UACs");
});
modelBuilder.Entity("vassago.Models.User", b =>
{
b.Property<Guid>("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
}
}
}

View File

@ -0,0 +1,131 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace vassago.Migrations
{
/// <inheritdoc />
public partial class UAC : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "UACs",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OwnerId = table.Column<Guid>(type: "uuid", nullable: false),
DisplayName = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UACs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AccountUAC",
columns: table => new
{
AccountInChannelsId = table.Column<Guid>(type: "uuid", nullable: false),
UACsId = table.Column<Guid>(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<Guid>(type: "uuid", nullable: false),
UACsId = table.Column<Guid>(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<Guid>(type: "uuid", nullable: false),
UsersId = table.Column<Guid>(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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AccountUAC");
migrationBuilder.DropTable(
name: "ChannelUAC");
migrationBuilder.DropTable(
name: "UACUser");
migrationBuilder.DropTable(
name: "UACs");
}
}
}

View File

@ -22,6 +22,51 @@ namespace vassago.Migrations
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("AccountUAC", b =>
{
b.Property<Guid>("AccountInChannelsId")
.HasColumnType("uuid");
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.HasKey("AccountInChannelsId", "UACsId");
b.HasIndex("UACsId");
b.ToTable("AccountUAC");
});
modelBuilder.Entity("ChannelUAC", b =>
{
b.Property<Guid>("ChannelsId")
.HasColumnType("uuid");
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.HasKey("ChannelsId", "UACsId");
b.HasIndex("UACsId");
b.ToTable("ChannelUAC");
});
modelBuilder.Entity("UACUser", b =>
{
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.Property<Guid>("UsersId")
.HasColumnType("uuid");
b.HasKey("UACsId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("UACUser");
});
modelBuilder.Entity("vassago.Models.Account", b => modelBuilder.Entity("vassago.Models.Account", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -180,6 +225,23 @@ namespace vassago.Migrations
b.ToTable("Messages"); b.ToTable("Messages");
}); });
modelBuilder.Entity("vassago.Models.UAC", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("DisplayName")
.HasColumnType("text");
b.Property<Guid>("OwnerId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("UACs");
});
modelBuilder.Entity("vassago.Models.User", b => modelBuilder.Entity("vassago.Models.User", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -191,6 +253,51 @@ namespace vassago.Migrations
b.ToTable("Users"); 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 => modelBuilder.Entity("vassago.Models.Account", b =>
{ {
b.HasOne("vassago.Models.User", "IsUser") b.HasOne("vassago.Models.User", "IsUser")

View File

@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection; using System.Reflection;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks;
public class Account public class Account
{ {
@ -28,6 +27,7 @@ public class Account
public bool IsBot { get; set; } //webhook counts public bool IsBot { get; set; } //webhook counts
public Channel SeenInChannel { get; set; } public Channel SeenInChannel { get; set; }
public string Protocol { get; set; } public string Protocol { get; set; }
public List<UAC> UACs { get; set; }
[JsonIgnore] [JsonIgnore]
public User IsUser {get; set;} public User IsUser {get; set;}
} }

View File

@ -35,6 +35,8 @@ public class Channel
public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; } public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; }
public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; } public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; }
public List<UAC> UACs { get; set; }
[NonSerialized] [NonSerialized]
public Func<string, string, Task> SendFile; public Func<string, string, Task> SendFile;

View File

@ -7,7 +7,7 @@ public class ChattingContext : DbContext
{ {
public DbSet<Attachment> Attachments { get; set; } public DbSet<Attachment> Attachments { get; set; }
public DbSet<Channel> Channels { get; set; } public DbSet<Channel> Channels { get; set; }
//public DbSet<Emoji> Emoji {get;set;} public DbSet<UAC> UACs { get; set; }
public DbSet<Message> Messages { get; set; } public DbSet<Message> Messages { get; set; }
public DbSet<Account> Accounts { get; set; } public DbSet<Account> Accounts { get; set; }
public DbSet<User> Users { get; set; } public DbSet<User> Users { get; set; }

23
Models/UAC.cs Normal file
View File

@ -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; }
///<summary indulgence="haiku-like">
///behaviors will have
///a hardcoded ID thing
///so they can find theirs.
///</summary>
public Guid OwnerId { get; set;}
public string DisplayName { get; set; }
public List<Account> AccountInChannels { get; set; }
public List<Channel> Channels { get; set; }
public List<User> Users { get; set; }
}

View File

@ -13,6 +13,8 @@ public class User
[DeleteBehavior(DeleteBehavior.Cascade)] [DeleteBehavior(DeleteBehavior.Cascade)]
public List<Account> Accounts { get; set; } public List<Account> Accounts { get; set; }
public List<UAC> 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. //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; } //public bool Tag_CanTwitchSummon { get; set; }
@ -30,7 +32,7 @@ public class User
} }
else else
{ {
return $"[accountless {Id}"; return $"[accountless {Id}]";
} }
} }
} }

View File

@ -162,4 +162,28 @@ public static class Rememberer
dbAccessSemaphore.Release(); dbAccessSemaphore.Release();
return toReturn; return toReturn;
} }
public static List<UAC> UACsOverview()
{
List<UAC> 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<Func<UAC, bool>> 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();
}
} }

View File

@ -25,10 +25,61 @@ public class HomeController : Controller
Console.WriteLine($"accounts: {allAccounts?.Count ?? 0}, channels: {allChannels?.Count ?? 0}"); Console.WriteLine($"accounts: {allAccounts?.Count ?? 0}, channels: {allChannels?.Count ?? 0}");
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append('['); sb.Append('[');
sb.Append("{text: \"channels\", expanded:true, nodes: [");
//UACs
var allUACs = Rememberer.UACsOverview();
var first = true; 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\": \"<a href=\\\"{Url.ActionLink(action: "Details", controller: "UACs", values: new {id = uac.Id})}\\\">{uac.DisplayName}</a>\"}}");
}
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); var topLevelChannels = Rememberer.ChannelsOverview().Where(x => x.ParentChannel == null);
first = true;
foreach (var topLevelChannel in topLevelChannels) foreach (var topLevelChannel in topLevelChannels)
{ {
if (first) if (first)
@ -84,28 +135,8 @@ public class HomeController : Controller
} }
sb.Append("]}"); sb.Append("]}");
} }
var users = Rememberer.UsersOverview();// _db.Users.ToList();
if(users.Any()) sb.Append("]");
{
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(']');
ViewData.Add("treeString", sb.ToString()); ViewData.Add("treeString", sb.ToString());
return View("Index"); return View("Index");
} }
@ -121,7 +152,6 @@ public class HomeController : Controller
if (currentChannel.SubChannels != null || theseAccounts != null) if (currentChannel.SubChannels != null || theseAccounts != null)
{ {
sb.Append(", \"nodes\": ["); sb.Append(", \"nodes\": [");
}
if (currentChannel.SubChannels != null) if (currentChannel.SubChannels != null)
{ {
foreach (var subChannel in currentChannel.SubChannels) foreach (var subChannel in currentChannel.SubChannels)
@ -159,7 +189,9 @@ public class HomeController : Controller
} }
sb.Append("]}"); sb.Append("]}");
} }
sb.Append("]}"); sb.Append(']');
}
sb.Append('}');
} }
private void serializeAccount(ref StringBuilder sb, Account currentAccount) private void serializeAccount(ref StringBuilder sb, Account currentAccount)
{ {
@ -171,17 +203,21 @@ public class HomeController : Controller
sb.Append(currentUser.DisplayName); sb.Append(currentUser.DisplayName);
sb.Append("</a>\", "); sb.Append("</a>\", ");
var ownedAccounts = allAccounts.Where(a => a.IsUser == currentUser); var ownedAccounts = allAccounts.Where(a => a.IsUser == currentUser);
if (ownedAccounts?.Count() > 0)
{
sb.Append("nodes: ["); sb.Append("nodes: [");
sb.Append($"{{\"text\": \"owned accounts:\", \"expanded\":true, \"nodes\": ["); sb.Append($"{{\"text\": \"owned accounts:\", \"expanded\":true, \"nodes\": [");
if (ownedAccounts != null) var first = true;
{
foreach (var acc in ownedAccounts) foreach (var acc in ownedAccounts)
{ {
serializeAccount(ref sb, acc); if(!first)
sb.Append(','); sb.Append(',');
serializeAccount(ref sb, acc);
first = false;
} }
sb.Append("]}]");
} }
sb.Append("]}]}"); sb.Append("}");
} }
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]

View File

@ -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 });
}
}

View File

@ -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<UACController> _logger;
public UACController(ILogger<UACController> 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);
}
}

View File

@ -7,7 +7,8 @@
<script type="text/javascript"> <script type="text/javascript">
function getTree() { function getTree() {
var tree = @Html.Raw(ViewData["treeString"]); var tree = @Html.Raw(ViewData["treeString"]);
console.log(tree); console.log('tree');
console.log('@ViewData["treeString"]');
return tree; return tree;
} }

View File

@ -0,0 +1,151 @@
@using System.ComponentModel
@using Newtonsoft.Json
@using System.Text;
@model UAC
<a href="/">home</a>/
@Html.Raw(ViewData["breadcrumbs"])
<table class="table">
<tbody>
<tr>
<th scope="row">Display Name</th>
<td>@Model.DisplayName</td>
</tr>
<tr>
<th scope="row">Channels</th>
<td>
@Html.Raw("<div id=\"channelsTree\"></div>")
</td>
</tr>
<tr>
<th scope="row">Users</th>
<td>
@Html.Raw("<div id=\"usersTree\"></div>")
</td>
</tr>
<tr>
<th scope="row">AccountInChannels</th>
<td>
@Html.Raw("<div id=\"accountsTree\"></div>")
</td>
</tr>
</tbody>
</table>
<div id="add-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Insert GUID</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>
<ul>
<li>//TODO: search</li>
</ul>
</p>
<p>
<input id="addmodaltext" type="text" />
</p>
</div>
<div class="modal-footer">
<button id="modalsubmit" type="button" class="btn btn-primary">Save changes</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@section Scripts{
<script type="text/javascript">
function addChannel(){
let modalbutton = document.querySelector("#add-modal button#modalsubmit");
modalbutton.onclick = addChannelSubmit;
$("#add-modal").modal("show");
}
function addChannelSubmit(){
let guid = document.querySelector("#add-modal #addmodaltext").value;
linkUAC_Channel(guid);
$("#add-modal").modal("hide");
console.log(guid);
}
function addUser(){
let modalbutton = document.querySelector("#add-modal button#modalsubmit");
modalbutton.onclick = addUserSubmit;
$("#add-modal").modal("show");
}
function addUserSubmit(){
let guid = document.querySelector("#add-modal #addmodaltext").value;
linkUAC_User(guid);
$("#add-modal").modal("hide");
console.log(guid);
}
function addAccount(){
let modalbutton = document.querySelector("#add-modal button#modalsubmit");
modalbutton.onclick = addAccountSubmit;
$("#add-modal").modal("show");
}
function addAccountSubmit(){
let guid = document.querySelector("#add-modal #addmodaltext").value;
linkUAC_Account(guid);
$("#add-modal").modal("hide");
console.log(guid);
}
function channelsTree() {
@{
var sb = new StringBuilder();
sb.Append("[{text: \"Channels\", \"expanded\":true, nodes: [");
sb.Append($"{{text: \"<button onclick=\\\"addChannel()\\\">add channel<button>\"}}");
foreach (var acc in Model.Channels?.OrderBy(a => a.DisplayName))
{
sb.Append(',');
sb.Append($"{{text: \"<a href=\\\"/Channels/Details/{acc.Id}\\\">{acc.DisplayName}</a> - <button type=\\\"button\\\" class=\\\"btn\\\" onclick=\\\"removeUser('{acc.Id}')\\\">remove (todo)</button>\"}}");
}
sb.Append("]}]");
}
var tree = @Html.Raw(sb.ToString());
return tree;
}
function usersTree() {
@{
sb = new StringBuilder();
sb.Append("[{text: \"Users\", \"expanded\":true, nodes: [");
sb.Append($"{{text: \"<button onclick=\\\"addUser()\\\">add user</button>\"}}");
foreach (var acc in Model.Users?.OrderBy(a => a.DisplayName))
{
sb.Append(',');
sb.Append($"{{text: \"<a href=\\\"/Users/Details/{acc.Id}\\\">{acc.DisplayName}</a> - <button type=\\\"button\\\" class=\\\"btn\\\" onclick=\\\"removeUser('{acc.Id}')\\\">remove (todo)</button>\"}}");
}
sb.Append("]}]");
}
var tree = @Html.Raw(sb.ToString());
return tree;
}
function accountsTree() {
@{
sb = new StringBuilder();
sb.Append("[{text: \"Accounts\", \"expanded\":true, nodes: [");
sb.Append($"{{text: \"<button onclick=\\\"addAccount()\\\">add account</button>\"}}");
foreach (var acc in Model.AccountInChannels?.OrderBy(a => a.DisplayName))
{
sb.Append(',');
sb.Append($"{{text: \"<a href=\\\"/Accounts/Details/{acc.Id}\\\">{acc.DisplayName}</a> - <button type=\\\"button\\\" class=\\\"btn\\\" onclick=\\\"removeAccounts('{acc.Id}')\\\">remove (todo)</button>\"}}");
}
sb.Append("]}]");
}
var tree = @Html.Raw(sb.ToString());
return tree;
}
$('#channelsTree').bstreeview({ data: channelsTree() });
$('#usersTree').bstreeview({ data: usersTree() });
$('#accountsTree').bstreeview({ data: accountsTree() });
</script>
}

View File

@ -19,12 +19,6 @@
<div id="accountsTree"></div> <div id="accountsTree"></div>
</td> </td>
</tr> </tr>
<tr>
<th scope="row">Permission Tags</th>
<td>
<div id="tagsTree"></div>
</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -65,26 +59,7 @@
return tree; 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: \"<input type=\\\"checkbox\\\" > is goated (w/ sauce)</input>\"}}");
first = false;
}
sb.Append("]}]");
}
console.log(@Html.Raw(sb.ToString()));
var tree = @Html.Raw(sb.ToString());
return tree;
}
$('#accountsTree').bstreeview({ data: getAccountsTree() }); $('#accountsTree').bstreeview({ data: getAccountsTree() });
$('#tagsTree').bstreeview({ data: getTagsTree() });
document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } }); document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } });
</script> </script>
} }

View File

@ -1,7 +1,5 @@
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification var apiUrl = '/api/';
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
function Account(displayName, accountId, protocol){ function Account(displayName, accountId, protocol){
this.displayName = displayName; this.displayName = displayName;
this.accountId = accountId; 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. //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? //you selfish fuck... What are you, fox?
//as it stands, you want something like /api/Channels/, trailing slash intentional //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 //structure the model your (dang) self into a nice object
console.log(model); console.log(model);
//i know the page url.
console.log(window.location.pathname); console.log(window.location.pathname);
var components = window.location.pathname.split('/'); var components = window.location.pathname.split('/');
if(components[2] !== "Details") // if(components[2] !== "Details")
{ // {
console.log("wtf are you doing? " + components[2] + " is something other than Details"); // console.log("wtf are you doing? " + components[2] + " is something other than Details");
} // }
var type=components[1]; 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("dexter impression: I am now ready to post the following content:");
console.log(JSON.stringify(model)); console.log(JSON.stringify(model));
fetch(apiUrl, { fetch(apiUrl + type + '/', {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json', '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('/'); var components = window.location.pathname.split('/');
if(components[2] !== "Details") // if(components[2] !== "Details")
{ // {
console.log("wtf are you doing? " + components[2] + " is something other than Details"); // console.log("wtf are you doing? " + components[2] + " is something other than Details");
} // }
var type=components[1]; var type=components[1];
var id=components[3]; // var id=components[3];
fetch(apiUrl, { fetch(apiUrl + type + '/', {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -78,3 +75,84 @@ function deleteModel(model, apiUrl)
console.error('Error:', error); console.error('Error:', error);
}); });
} }
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);
});
}