Compare commits

...

3 Commits

Author SHA1 Message Date
6881816c94 self referencing serialization ignored
Some checks failed
gitea.arg.rip/vassago/pipeline/head There was a failure building this commit
2025-03-12 16:05:22 -04:00
53753374f0 runs and listens without exploding 2025-03-11 22:20:09 -04:00
50ecfc5867 double add solved. also, i was relinking... I think that's supposed to have been only if needed. 2025-03-11 13:33:00 -04:00
7 changed files with 111 additions and 46 deletions

View File

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

View File

@ -7,6 +7,7 @@ using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using static vassago.Models.Enumerations;
public class Channel
@ -17,6 +18,7 @@ public class Channel
public string DisplayName { get; set; }
[DeleteBehavior(DeleteBehavior.Cascade)]
public List<Channel> SubChannels { get; set; }
[JsonIgnore]
public Channel ParentChannel { get; set; }
public string Protocol { get; set; }
[DeleteBehavior(DeleteBehavior.Cascade)]
@ -82,6 +84,23 @@ public class Channel
}
}
}
///<summary>
///break self-referencing loops for library-agnostic serialization
///</summary>
public Channel AsSerializable()
{
var toReturn = this.MemberwiseClone() as Channel;
toReturn.ParentChannel = null;
if(toReturn.Users?.Count > 0)
{
foreach (var account in toReturn.Users)
{
account.SeenInChannel = null;
}
}
return toReturn;
}
}
public class DefinitePermissionSettings

View File

@ -11,7 +11,10 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IHostedService, vassago.ConsoleService>();
builder.Services.AddDbContext<ChattingContext>();
builder.Services.AddControllers().AddNewtonsoftJson();
builder.Services.AddControllers().AddNewtonsoftJson(options => {
options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
builder.Services.AddProblemDetails();
builder.Services.Configure<RazorViewEngineOptions>(o => {
o.ViewLocationFormats.Clear();

View File

@ -237,10 +237,12 @@ public class DiscordInterface
if (c == null)
{
Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}");
c = new Channel();
c = new Channel()
{
Users = []
};
}
c.DisplayName = channel.Name;
c.ExternalId = channel.Id.ToString();
c.ChannelType = (channel is IPrivateChannel) ? vassago.Models.Enumerations.ChannelType.DM : vassago.Models.Enumerations.ChannelType.Normal;
c.Messages ??= [];
@ -263,7 +265,11 @@ public class DiscordInterface
switch (c.ChannelType)
{
case vassago.Models.Enumerations.ChannelType.DM:
c.DisplayName = "DM: " + (channel as IPrivateChannel).Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id).Username;
var asPriv =(channel as IPrivateChannel);
c.DisplayName = "DM: " + asPriv?.Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id).Username;
break;
default:
c.DisplayName = channel.Name;
break;
}
@ -286,12 +292,23 @@ public class DiscordInterface
Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg");
}
parentChannel.SubChannels ??= [];
parentChannel.SubChannels.Add(c);
if(!parentChannel.SubChannels.Contains(c))
{
parentChannel.SubChannels.Add(c);
}
c.SendMessage = (t) => { return channel.SendMessageAsync(t); };
c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); };
return Rememberer.RememberChannel(c);
c = Rememberer.RememberChannel(c);
var selfAccountInChannel = c.Users.FirstOrDefault(a => a.ExternalId == client.CurrentUser.Id.ToString());
if(selfAccountInChannel == null)
{
selfAccountInChannel = UpsertAccount(client.CurrentUser, c);
}
return c;
}
internal Channel UpsertChannel(IGuild channel)
{
@ -316,24 +333,25 @@ public class DiscordInterface
return Rememberer.RememberChannel(c);
}
internal static Account UpsertAccount(IUser user, Channel inChannel)
internal static Account UpsertAccount(IUser discordUser, Channel inChannel)
{
var acc = Rememberer.SearchAccount(ui => ui.ExternalId == user.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id);
var acc = Rememberer.SearchAccount(ui => ui.ExternalId == discordUser.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id);
Console.WriteLine($"upserting account, retrieved {acc?.Id}.");
if (acc != null)
{
Console.WriteLine($"acc's user: {acc.IsUser?.Id}");
}
acc ??= new Account() { IsUser = new User() };
acc ??= new Account() {
IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == discordUser.Id.ToString() && a.Protocol == PROTOCOL))
?? new User()
};
acc.Username = user.Username;
acc.ExternalId = user.Id.ToString();
acc.IsBot = user.IsBot || user.IsWebhook;
acc.Username = discordUser.Username;
acc.ExternalId = discordUser.Id.ToString();
acc.IsBot = discordUser.IsBot || discordUser.IsWebhook;
acc.Protocol = PROTOCOL;
acc.SeenInChannel = inChannel;
acc.IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol));
Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}");
if (acc.IsUser != null)
{
@ -344,8 +362,13 @@ public class DiscordInterface
{
Console.WriteLine($"channel has {inChannel.Users.Count} accounts");
}
inChannel.Users ??= [acc];
Rememberer.RememberAccount(acc);
inChannel.Users ??= [];
if(!inChannel.Users.Contains(acc))
{
inChannel.Users.Add(acc);
Rememberer.RememberChannel(inChannel);
}
return acc;
}

View File

@ -9,27 +9,27 @@ public static class Rememberer
private static readonly ChattingContext db = new();
public static Account SearchAccount(Expression<Func<Account, bool>> predicate)
{
return (new ChattingContext()).Accounts.Include(a => a.IsUser).FirstOrDefault(predicate);
return db.Accounts.Include(a => a.IsUser).FirstOrDefault(predicate);
}
public static List<Account> SearchAccounts(Expression<Func<Account, bool>> predicate)
{
return (new ChattingContext()).Accounts.Where(predicate).ToList();
return db.Accounts.Where(predicate).ToList();
}
public static Attachment SearchAttachment(Expression<Func<Attachment, bool>> predicate)
{
return (new ChattingContext()).Attachments.FirstOrDefault(predicate);
return db.Attachments.FirstOrDefault(predicate);
}
public static Channel SearchChannel(Expression<Func<Channel, bool>> predicate)
{
return (new ChattingContext()).Channels.FirstOrDefault(predicate);
return db.Channels.FirstOrDefault(predicate);
}
public static Message SearchMessage(Expression<Func<Message, bool>> predicate)
{
return (new ChattingContext()).Messages.FirstOrDefault(predicate);
return db.Messages.FirstOrDefault(predicate);
}
public static User SearchUser(Expression<Func<User, bool>> predicate)
{
return (new ChattingContext()).Users.Include(u => u.Accounts).FirstOrDefault(predicate);
return db.Users.Include(u => u.Accounts).FirstOrDefault(predicate);
}
public static void RememberAccount(Account toRemember)
{
@ -61,6 +61,27 @@ public static class Rememberer
db.SaveChanges();
}
public static void ForgetAccount(Account toForget)
{
var user = toForget.IsUser;
var usersOnlyAccount = user.Accounts?.Count == 1;
if(usersOnlyAccount)
{
Rememberer.ForgetUser(user);
}
else
{
db.Accounts.Remove(toForget);
db.SaveChanges();
}
}
public static void ForgetChannel(Channel toForget)
{
db.Channels.Remove(toForget);
db.SaveChanges();
}
public static void ForgetUser(User toForget)
{
db.Users.Remove(toForget);
@ -69,11 +90,11 @@ public static class Rememberer
}
public static List<Account> AccountsOverview()
{
return db.Accounts.ToList();
return [..db.Accounts];
}
public static List<Channel> ChannelsOverview()
{
return db.Channels.Include(u => u.SubChannels).Include(c => c.ParentChannel).ToList();
return [..db.Channels.Include(u => u.SubChannels).Include(c => c.ParentChannel)];
}
public static Channel ChannelDetail(Guid Id)
{

View File

@ -25,7 +25,7 @@ public class HomeController : Controller
Console.WriteLine($"accounts: {allAccounts?.Count ?? 0}, channels: {allChannels?.Count ?? 0}");
var sb = new StringBuilder();
sb.Append('[');
sb.Append("{text: \"channels\", nodes: [");
sb.Append("{text: \"channels\", expanded:true, nodes: [");
var first = true;
var topLevelChannels = Rememberer.ChannelsOverview().Where(x => x.ParentChannel == null);
@ -46,7 +46,7 @@ public class HomeController : Controller
if (allChannels.Any())
{
sb.Append(",{text: \"orphaned channels\", nodes: [");
sb.Append(",{text: \"orphaned channels\", expanded:true, nodes: [");
first = true;
while (true)
{
@ -68,7 +68,7 @@ public class HomeController : Controller
}
if (allAccounts.Any())
{
sb.Append(",{text: \"channelless accounts\", nodes: [");
sb.Append(",{text: \"channelless accounts\", expanded:true, nodes: [");
first = true;
foreach (var acc in allAccounts)
{
@ -87,7 +87,7 @@ public class HomeController : Controller
var users = Rememberer.UsersOverview();// _db.Users.ToList();
if(users.Any())
{
sb.Append(",{text: \"users\", nodes: [");
sb.Append(",{text: \"users\", expanded:true, nodes: [");
first=true;
//refresh list; we'll be knocking them out again in serializeUser
allAccounts = Rememberer.AccountsOverview();
@ -105,7 +105,7 @@ public class HomeController : Controller
}
sb.Append("]}");
}
sb.Append("]");
sb.Append(']');
ViewData.Add("treeString", sb.ToString());
return View("Index");
}
@ -114,6 +114,7 @@ public class HomeController : Controller
allChannels.Remove(currentChannel);
//"but adam", you say, "there's an href attribute, why make a link?" because that makes the entire bar a link, and trying to expand the node will probably click the link
sb.Append($"{{\"text\": \"<a href=\\\"{Url.ActionLink(action: "Details", controller: "Channels", values: new {id = currentChannel.Id})}\\\">{currentChannel.DisplayName}</a>\"");
sb.Append(", expanded:true ");
var theseAccounts = allAccounts.Where(a => a.SeenInChannel?.Id == currentChannel.Id).ToList();
allAccounts.RemoveAll(a => a.SeenInChannel?.Id == currentChannel.Id);
var first = true;
@ -123,7 +124,7 @@ public class HomeController : Controller
}
if (currentChannel.SubChannels != null)
{
foreach (var subChannel in currentChannel.SubChannels ?? new List<Channel>())
foreach (var subChannel in currentChannel.SubChannels)
{
if (first)
{
@ -135,7 +136,7 @@ public class HomeController : Controller
}
serializeChannel(ref sb, ref allChannels, ref allAccounts, subChannel);
}
if (theseAccounts != null)
if (theseAccounts != null && !first) //"first" here tells us that we have at least one subchannel
{
sb.Append(',');
}

View File

@ -11,49 +11,50 @@ namespace vassago.Controllers.api;
public class ChannelsController : ControllerBase
{
private readonly ILogger<ChannelsController> _logger;
private readonly ChattingContext _db;
public ChannelsController(ILogger<ChannelsController> logger, ChattingContext db)
public ChannelsController(ILogger<ChannelsController> logger)
{
_logger = logger;
_db = db;
}
[HttpGet("{id}")]
[Produces("application/json")]
public Channel Get(Guid id)
{
return _db.Find<Channel>(id);
return Rememberer.ChannelDetail(id);
}
[HttpPatch]
[Produces("application/json")]
public IActionResult Patch([FromBody] Channel channel)
{
var fromDb = _db.Channels.Find(channel.Id);
var fromDb = Rememberer.ChannelDetail(channel.Id);
if (fromDb == null)
{
_logger.LogError($"attempt to update channel {channel.Id}, not found");
return NotFound();
}
else
{
_logger.LogDebug($"patching {channel.DisplayName} (id: {channel.Id})");
}
//settable values: lewdness filter level, meanness filter level. maybe i could decorate them...
fromDb.LewdnessFilterLevel = channel.LewdnessFilterLevel;
fromDb.MeannessFilterLevel = channel.MeannessFilterLevel;
_db.SaveChanges();
Rememberer.RememberChannel(fromDb);
return Ok(fromDb);
}
[HttpDelete]
[Produces("application/json")]
public IActionResult Delete([FromBody] Channel channel)
{
var fromDb = _db.Channels.Find(channel.Id);
var fromDb = Rememberer.ChannelDetail(channel.Id);
if (fromDb == null)
{
_logger.LogError($"attempt to delete channel {channel.Id}, not found");
return NotFound();
}
deleteChannel(fromDb);
_db.SaveChanges();
return Ok();
}
private void deleteChannel(Channel channel)
@ -74,21 +75,16 @@ public class ChannelsController : ControllerBase
}
}
if(channel.Messages?.Count > 0)
{
_db.Remove(channel.Messages);
}
_db.Remove(channel);
Rememberer.ForgetChannel(channel);
}
private void deleteAccount(Account account)
{
var user = account.IsUser;
var usersOnlyAccount = user.Accounts?.Count == 1;
_db.Remove(account);
Rememberer.ForgetAccount(account);
if(usersOnlyAccount)
_db.Users.Remove(user);
Rememberer.ForgetUser(user);
}
}