diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index 1fd7a38cd..1461cc225 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -55,7 +55,7 @@ public interface IAgentService string GetDataDir(); string GetAgentDataDir(string agentId); - List GetAgentsByUser(string userId); + Task> GetUserAgents(string userId); PluginDef GetPlugin(string agentId); diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/UserAgent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/UserAgent.cs index b6eab94db..a130f9b2d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/UserAgent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/UserAgent.cs @@ -1,11 +1,27 @@ +using BotSharp.Abstraction.Users.Models; + namespace BotSharp.Abstraction.Agents.Models; public class UserAgent { + [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; + + [JsonPropertyName("user_id")] public string UserId { get; set; } = string.Empty; - public string AgentId { get; set; } = string.Empty; - public bool Editable { get; set; } + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; + + [JsonIgnore] + public Agent? Agent { get; set; } + + [JsonPropertyName("updated_time")] public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; + + [JsonPropertyName("created_time")] public DateTime CreatedTime { get; set; } = DateTime.UtcNow; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 003eddf49..4f604dc42 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -10,9 +10,6 @@ namespace BotSharp.Abstraction.Repositories; public interface IBotSharpRepository { - int Transaction(Action action); - void Add(object entity); - #region Plugin PluginConfig GetPluginConfig(); void SavePluginConfig(PluginConfig config); @@ -35,13 +32,15 @@ public interface IBotSharpRepository void UpdateUserPhone(string userId, string Iphone, string regionCode) => throw new NotImplementedException(); void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException(); void UpdateUsersIsDisable(List userIds, bool isDisable) => throw new NotImplementedException(); + PagedItems GetUsers(UserFilter filter) => throw new NotImplementedException(); + bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException(); #endregion #region Agent void UpdateAgent(Agent agent, AgentField field); Agent? GetAgent(string agentId); List GetAgents(AgentFilter filter); - List GetAgentsByUser(string userId); + List GetUserAgents(string userId); void BulkInsertAgents(List agents); void BulkInsertUserAgents(List userAgents); bool DeleteAgents(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs new file mode 100644 index 000000000..4838e757f --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserAction.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Users.Enums; + +public static class UserAction +{ + public const string Edit = "edit"; + public const string Chat = "chat"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserConstant.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserConstant.cs new file mode 100644 index 000000000..9fde53765 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserConstant.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.Users.Enums; + +public static class UserConstant +{ + public static IEnumerable AdminRoles = new List + { + UserRole.Admin, + UserRole.Root + }; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs new file mode 100644 index 000000000..58dfc186b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserPermission.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Users.Enums; + +public static class UserPermission +{ + public const string CreateAgent = "create-agent"; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs index cddabe10e..0bde3b082 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Enums/UserRole.cs @@ -35,4 +35,4 @@ public class UserRole public const string Assistant = "assistant"; public const string Root = "root"; -} +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 8123b2726..2a869d02a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -6,6 +6,8 @@ namespace BotSharp.Abstraction.Users; public interface IUserService { Task GetUser(string id); + Task> GetUsers(UserFilter filter); + Task UpdateUser(User model, bool isUpdateUserAgents = false); Task CreateUser(User user); Task ActiveUser(UserActivationModel model); Task GetAffiliateToken(string authorization); diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs index 0baa7457b..8d136dc54 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs @@ -25,6 +25,10 @@ public class User public string? AffiliateId { get; set; } public string? EmployeeId { get; set; } public bool IsDisabled { get; set; } + public IEnumerable Permissions { get; set; } = []; + + [JsonIgnore] + public IEnumerable AgentActions { get; set; } = []; public DateTime UpdatedTime { get; set; } = DateTime.UtcNow; public DateTime CreatedTime { get; set; } = DateTime.UtcNow; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAgentAction.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAgentAction.cs new file mode 100644 index 000000000..1ac759a1e --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserAgentAction.cs @@ -0,0 +1,16 @@ +namespace BotSharp.Abstraction.Users.Models; + +public class UserAgentAction +{ + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonIgnore] + public Agent? Agent { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs new file mode 100644 index 000000000..73e1794dc --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs @@ -0,0 +1,19 @@ +namespace BotSharp.Abstraction.Users.Models; + +public class UserFilter : Pagination +{ + [JsonPropertyName("user_ids")] + public IEnumerable? UserIds { get; set; } + + [JsonPropertyName("user_names")] + public IEnumerable? UserNames { get; set; } + + [JsonPropertyName("external_ids")] + public IEnumerable? ExternalIds { get; set; } + + [JsonPropertyName("roles")] + public IEnumerable? Roles { get; set; } + + [JsonPropertyName("sources")] + public IEnumerable? Sources { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs index 43bd9c7f4..2f51f1dd7 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs @@ -45,7 +45,7 @@ public bool AttachMenu(List menu) SubMenu = new List { new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin" - new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List { UserRole.Admin } }, // icon: "bx bx-task" + new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List { UserRole.Root, UserRole.Admin } }, // icon: "bx bx-task" new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot" } }); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs index 9e2e1d5fb..3c9e6bf2e 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs @@ -8,14 +8,14 @@ public partial class AgentService { public async Task CreateAgent(Agent agent) { - var agentRecord = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Name.IsEqualTo(agent.Name)); - - if (agentRecord != null) + var userAgents = _db.GetUserAgents(_user.Id); + var found = userAgents?.FirstOrDefault(x => x.Agent != null && x.Agent.Name.IsEqualTo(agent.Name)); + if (found != null) { - return agentRecord; + return found.Agent; } - agentRecord = Agent.Clone(agent); + var agentRecord = Agent.Clone(agent); agentRecord.Id = Guid.NewGuid().ToString(); agentRecord.CreatedDateTime = DateTime.UtcNow; agentRecord.UpdatedDateTime = DateTime.UtcNow; @@ -24,21 +24,7 @@ public async Task CreateAgent(Agent agent) var agentSettings = _services.GetRequiredService(); var user = _db.GetUserById(_user.Id); - var userAgentRecord = new UserAgent - { - Id = Guid.NewGuid().ToString(), - UserId = user.Id, - AgentId = agentRecord.Id, - Editable = false, - CreatedTime = DateTime.UtcNow, - UpdatedTime = DateTime.UtcNow - }; - - _db.Transaction(delegate - { - _db.Add(agentRecord); - _db.Add(userAgentRecord); - }); + _db.BulkInsertAgents(new List { agentRecord }); Utilities.ClearCache(); return await Task.FromResult(agentRecord); @@ -213,17 +199,4 @@ private List GetTasksFromFile(string fileDir) task.Content = content.Substring(suffix.Length).Trim(); return task; } - - private UserAgent BuildUserAgent(string agentId, string userId, bool editable = false) - { - return new UserAgent - { - Id = Guid.NewGuid().ToString(), - UserId = userId, - AgentId = agentId, - Editable = editable, - CreatedTime = DateTime.UtcNow, - UpdatedTime = DateTime.UtcNow - }; - } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs index 1fe5c6e1a..8a05542ea 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.DeleteAgent.cs @@ -7,9 +7,10 @@ public partial class AgentService public async Task DeleteAgent(string id) { var user = _db.GetUserById(_user.Id); - var agent = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Id.IsEqualTo(id)); + var userAgents = await GetUserAgents(user?.Id); + var found = userAgents?.FirstOrDefault(x => x.AgentId == id); - if (user?.Role != UserRole.Admin && agent == null) + if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || !found.Actions.Contains(UserAction.Edit))) { return false; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs index 10598d4e9..e4a311d24 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.RefreshAgents.cs @@ -54,7 +54,6 @@ public async Task RefreshAgents() .SetResponses(responses) .SetSamples(samples); - var userAgent = BuildUserAgent(agent.Id, user.Id); var tasks = GetTasksFromFile(dir); var isAgentDeleted = _db.DeleteAgent(agent.Id); @@ -62,7 +61,6 @@ public async Task RefreshAgents() { await Task.Delay(100); _db.BulkInsertAgents(new List { agent }); - _db.BulkInsertUserAgents(new List { userAgent }); _db.BulkInsertAgentTasks(tasks); refreshedAgents.Add(agent.Name); _logger.LogInformation($"Agent {agent.Name} has been migrated."); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index fe532e28f..17aa4aa7a 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -9,13 +9,18 @@ public partial class AgentService { public async Task UpdateAgent(Agent agent, AgentField updateField) { + if (agent == null || string.IsNullOrEmpty(agent.Id)) return; + var userService = _services.GetRequiredService(); var user = await userService.GetUser(_user.Id); - var userAgents = GetAgentsByUser(user?.Id); - var editable = userAgents?.Select(x => x.Id)?.Contains(agent.Id) ?? false; - if (user?.Role != UserRole.Admin && !editable) return; - if (agent == null || string.IsNullOrEmpty(agent.Id)) return; + var userAgents = await GetUserAgents(user.Id); + var found = userAgents?.FirstOrDefault(x => x.AgentId == agent.Id); + + if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || found.Actions.Contains(UserAction.Edit))) + { + return; + } var record = _db.GetAgent(agent.Id); if (record == null) return; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs index b69f75297..a48201d8d 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -49,10 +49,12 @@ public string GetAgentDataDir(string agentId) return dir; } - public List GetAgentsByUser(string userId) + public async Task> GetUserAgents(string userId) { - var agents = _db.GetAgentsByUser(userId); - return agents; + if (string.IsNullOrEmpty(userId)) return []; + + var userAgents = _db.GetUserAgents(userId); + return userAgents; } public IEnumerable GetAgentUtilities() diff --git a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs index a4131e64e..80c37f7a5 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/BotSharpDbContext.cs @@ -2,71 +2,12 @@ using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Tasks.Models; using BotSharp.Abstraction.Translation.Models; -using BotSharp.Abstraction.Users.Models; using BotSharp.Abstraction.VectorStorage.Models; -using Microsoft.EntityFrameworkCore.Infrastructure; namespace BotSharp.Core.Repository; public class BotSharpDbContext : Database, IBotSharpRepository { - public IQueryable Users => throw new NotImplementedException(); - - public IQueryable Agents => throw new NotImplementedException(); - - public IQueryable UserAgents => throw new NotImplementedException(); - - public IQueryable Conversations => throw new NotImplementedException(); - - public int Transaction(Action action) - { - DatabaseFacade database = base.GetMaster(typeof(TTableInterface)).Database; - int num = 0; - if (database.CurrentTransaction == null) - { - using (Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction dbContextTransaction = database.BeginTransaction()) - { - try - { - action(); - num = base.SaveChanges(); - dbContextTransaction.Commit(); - return num; - } - catch (Exception ex) - { - dbContextTransaction.Rollback(); - if (ex.Message.Contains("See the inner exception for details")) - { - throw ex.InnerException; - } - - throw ex; - } - } - } - - try - { - action(); - return base.SaveChanges(); - } - catch (Exception ex2) - { - if (database.CurrentTransaction != null) - { - database.CurrentTransaction.Rollback(); - } - - if (ex2.Message.Contains("See the inner exception for details")) - { - throw ex2.InnerException; - } - - throw ex2; - } - } - #region Plugin public PluginConfig GetPluginConfig() => throw new NotImplementedException(); public void SavePluginConfig(PluginConfig config) => throw new NotImplementedException(); @@ -79,7 +20,7 @@ public Agent GetAgent(string agentId) public List GetAgents(AgentFilter filter) => throw new NotImplementedException(); - public List GetAgentsByUser(string userId) + public List GetUserAgents(string userId) => throw new NotImplementedException(); public void UpdateAgent(Agent agent, AgentField field) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index bb1372790..90f05ceb9 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -1,5 +1,5 @@ -using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Routing.Models; +using BotSharp.Abstraction.Users.Models; using System.IO; namespace BotSharp.Core.Repository @@ -386,19 +386,26 @@ public List GetAgents(AgentFilter filter) return query.ToList(); } - public List GetAgentsByUser(string userId) + public List GetUserAgents(string userId) { - var agentIds = (from ua in UserAgents - join u in Users on ua.UserId equals u.Id - where ua.UserId == userId || u.ExternalId == userId - select ua.AgentId).ToList(); + var found = (from ua in UserAgents + join u in Users on ua.UserId equals u.Id + where ua.UserId == userId || u.ExternalId == userId + select ua).ToList(); - var filter = new AgentFilter + if (found.IsNullOrEmpty()) return []; + + var agentIds = found.Select(x => x.AgentId).Distinct().ToList(); + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + foreach (var item in found) { - AgentIds = agentIds - }; - var agents = GetAgents(filter); - return agents; + var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (agent == null) continue; + + item.Agent = agent; + } + + return found; } @@ -442,9 +449,15 @@ public bool PatchAgentTemplate(string agentId, AgentTemplate template) return true; } - public void BulkInsertAgents(List agents) { } + public void BulkInsertAgents(List agents) + { + _agents = []; + } - public void BulkInsertUserAgents(List userAgents) { } + public void BulkInsertUserAgents(List userAgents) + { + _userAgents = []; + } public bool DeleteAgents() { @@ -473,14 +486,15 @@ public bool DeleteAgent(string agentId) var userAgents = JsonSerializer.Deserialize>(text, _options); if (userAgents.IsNullOrEmpty()) continue; - userAgents = userAgents.Where(x => x.AgentId != agentId).ToList(); + userAgents = userAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? []; File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options)); } } // Delete agent folder Directory.Delete(agentDir, true); - + _agents = []; + _userAgents = []; return true; } catch diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Transaction.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Transaction.cs deleted file mode 100644 index 13152f93b..000000000 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Transaction.cs +++ /dev/null @@ -1,82 +0,0 @@ -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Users.Models; -using System.IO; - -namespace BotSharp.Core.Repository; - -public partial class FileRepository -{ - public void Add(object entity) - { - if (entity is Agent agent) - { - _agents.Add(agent); - _changedTableNames.Add(nameof(Agent)); - } - else if (entity is User user) - { - _users.Add(user); - _changedTableNames.Add(nameof(User)); - } - else if (entity is UserAgent userAgent) - { - _userAgents.Add(userAgent); - _changedTableNames.Add(nameof(UserAgent)); - } - } - - private readonly List _changedTableNames = new List(); - public int Transaction(Action action) - { - _changedTableNames.Clear(); - action(); - - // Persist to disk - foreach (var table in _changedTableNames) - { - if (table == nameof(Agent)) - { - foreach (var agent in _agents) - { - var dir = Path.Combine(_dbSettings.FileRepository, _agentSettings.DataDir, agent.Id); - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - var path = Path.Combine(dir, AGENT_FILE); - File.WriteAllText(path, JsonSerializer.Serialize(agent, _options)); - } - } - else if (table == nameof(User)) - { - foreach (var user in _users) - { - var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, user.Id); - if (!Directory.Exists(dir)) - { - Directory.CreateDirectory(dir); - } - var path = Path.Combine(dir, USER_FILE); - File.WriteAllText(path, JsonSerializer.Serialize(user, _options)); - } - } - else if (table == nameof(UserAgent)) - { - _userAgents.GroupBy(x => x.UserId) - .Select(x => x.Key).ToList() - .ForEach(uid => - { - var agents = _userAgents.Where(x => x.UserId == uid).ToList(); - if (agents.Any()) - { - var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, uid); - var path = Path.Combine(dir, USER_AGENT_FILE); - File.WriteAllText(path, JsonSerializer.Serialize(agents, _options)); - } - }); - } - } - - return _changedTableNames.Count; - } -} diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index b7c8aae61..80ccacc40 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -1,5 +1,7 @@ +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; +using System; using System.IO; namespace BotSharp.Core.Repository; @@ -68,4 +70,100 @@ public void UpdateUserVerified(string userId) var path = Path.Combine(dir, USER_FILE); File.WriteAllText(path, JsonSerializer.Serialize(user, _options)); } + + public PagedItems GetUsers(UserFilter filter) + { + var users = Users; + + // Apply filters + if (!filter.UserIds.IsNullOrEmpty()) + { + users = users.Where(x => filter.UserIds.Contains(x.Id)); + } + if (!filter.UserNames.IsNullOrEmpty()) + { + users = users.Where(x => filter.UserNames.Contains(x.UserName)); + } + if (!filter.ExternalIds.IsNullOrEmpty()) + { + users = users.Where(x => filter.ExternalIds.Contains(x.ExternalId)); + } + if (!filter.Roles.IsNullOrEmpty()) + { + users = users.Where(x => filter.Roles.Contains(x.Role)); + } + if (!filter.Sources.IsNullOrEmpty()) + { + users = users.Where(x => filter.Sources.Contains(x.Source)); + } + + // Get user agents + var userIds = users.Select(x => x.Id).ToList(); + var userAgents = UserAgents.Where(x => userIds.Contains(x.UserId)).ToList(); + var agentIds = userAgents?.Select(x => x.AgentId)?.Distinct()?.ToList() ?? []; + + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + foreach (var item in userAgents) + { + item.Agent = agents.FirstOrDefault(x => x.Id == item.AgentId); + } + + foreach (var user in users) + { + var found = userAgents.Where(x => x.UserId == user.Id).ToList(); + if (found.IsNullOrEmpty()) continue; + + user.AgentActions = found.Select(x => new UserAgentAction + { + Id = x.Id, + AgentId = x.AgentId, + Agent = x.Agent, + Actions = x.Actions + }); + } + } + + return new PagedItems + { + Items = users.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size), + Count = users.Count() + }; + } + + public bool UpdateUser(User user, bool isUpdateUserAgents = false) + { + if (string.IsNullOrEmpty(user?.Id)) return false; + + var dir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER, user.Id); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + var userFile = Path.Combine(dir, USER_FILE); + user.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(userFile, JsonSerializer.Serialize(user, _options)); + + if (isUpdateUserAgents) + { + var userAgents = user.AgentActions?.Select(x => new UserAgent + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + UserId = user.Id, + AgentId = x.AgentId, + Actions = x.Actions ?? [], + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + })?.ToList() ?? []; + + var userAgentFile = Path.Combine(dir, USER_AGENT_FILE); + File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options)); + } + + _users = []; + _userAgents = []; + return true; + } } diff --git a/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs b/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs index 27c55ab5a..7f907fd1d 100644 --- a/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs @@ -22,7 +22,7 @@ public bool AttachMenu(List menu) var section = menu.First(x => x.Label == "Apps"); menu.Add(new PluginMenuDef("Task", link: "page/task", icon: "bx bx-task", weight: section.Weight + 8) { - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } }); return true; diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index f7f4a6de7..5bbd3c326 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -400,6 +400,19 @@ public async Task GetUser(string id) return user; } + public async Task> GetUsers(UserFilter filter) + { + var db = _services.GetRequiredService(); + var users = db.GetUsers(filter); + return users; + } + + public async Task UpdateUser(User model, bool isUpdateUserAgents = false) + { + var db = _services.GetRequiredService(); + return db.UpdateUser(model, isUpdateUserAgents); + } + public async Task ActiveUser(UserActivationModel model) { var id = model.UserName; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs index f033e31c1..54c9b75e9 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Users.Enums; namespace BotSharp.OpenAPI.Controllers; @@ -58,24 +59,64 @@ public AgentSettings GetSettings() } var editable = true; + var chatable = true; var userService = _services.GetRequiredService(); var user = await userService.GetUser(_user.Id); - if (user?.Role != UserRole.Admin) + if (!UserConstant.AdminRoles.Contains(user?.Role)) { - var userAgents = _agentService.GetAgentsByUser(user?.Id); - editable = userAgents?.Select(x => x.Id)?.Contains(targetAgent.Id) ?? false; + var userAgents = await _agentService.GetUserAgents(user?.Id); + var actions = userAgents?.FirstOrDefault(x => x.AgentId == targetAgent.Id)?.Actions ?? []; + editable = actions.Contains(UserAction.Edit); + chatable = actions.Contains(UserAction.Chat); } targetAgent.Editable = editable; + targetAgent.Chatable = chatable; return targetAgent; } [HttpGet("/agents")] - public async Task> GetAgents([FromQuery] AgentFilter filter) + public async Task> GetAgents([FromQuery] AgentFilter filter, [FromQuery] bool checkAuth = false) { var agentSetting = _services.GetRequiredService(); + var userService = _services.GetRequiredService(); + + List agents; var pagedAgents = await _agentService.GetAgents(filter); - var agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? new List(); + + if (!checkAuth) + { + agents = pagedAgents?.Items?.Select(x => AgentViewModel.FromAgent(x))?.ToList() ?? []; + return new PagedItems + { + Items = agents, + Count = pagedAgents?.Count ?? 0 + }; + } + + var userAgents = new List(); + var user = await userService.GetUser(_user.Id); + if (!UserConstant.AdminRoles.Contains(user.Role)) + { + userAgents = await _agentService.GetUserAgents(user.Id); + } + + agents = pagedAgents?.Items?.Select(x => + { + var chatable = true; + var editable = true; + if (!UserConstant.AdminRoles.Contains(user.Role)) + { + var actions = userAgents.FirstOrDefault(a => a.AgentId == x.Id)?.Actions ?? []; + chatable = actions.Contains(UserAction.Chat); + editable = actions.Contains(UserAction.Edit); + } + + var model = AgentViewModel.FromAgent(x); + model.Editable = editable; + model.Chatable = chatable; + return model; + })?.ToList() ?? []; return new PagedItems { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 91617a0f3..19f6241f7 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -55,7 +55,7 @@ public async Task> GetConversations([FromBody] return new PagedItems(); } - filter.UserId = user.Role != UserRole.Admin ? user.Id : filter.UserId; + filter.UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : filter.UserId; var conversations = await convService.GetConversations(filter); var agentService = _services.GetRequiredService(); var list = conversations.Items.Select(x => ConversationViewModel.FromSession(x)).ToList(); @@ -146,7 +146,7 @@ public async Task> GetDialogs([FromRoute] string var filter = new ConversationFilter { Id = conversationId, - UserId = user.Role != UserRole.Admin ? user.Id : null + UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null }; var conversations = await service.GetConversations(filter); if (conversations.Items.IsNullOrEmpty()) @@ -209,7 +209,7 @@ public async Task UpdateConversationTitle([FromRoute] string conversationI var filter = new ConversationFilter { Id = conversationId, - UserId = user.Role != UserRole.Admin ? user.Id : null + UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null }; var conversations = await conv.GetConversations(filter); @@ -262,7 +262,7 @@ public async Task DeleteConversation([FromRoute] string conversationId) var filter = new ConversationFilter { Id = conversationId, - UserId = user.Role != UserRole.Admin ? user.Id : null + UserId = !UserConstant.AdminRoles.Contains(user?.Role) ? user.Id : null }; var conversations = await conversationService.GetConversations(filter); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs index 342f39fb4..e4ec4aa82 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/PluginController.cs @@ -24,7 +24,7 @@ public async Task> GetPlugins([FromQuery] PluginFilter fil { var userService = _services.GetRequiredService(); var user = await userService.GetUser(_user.Id); - if (user?.Role != UserRole.Admin) + if (!UserConstant.AdminRoles.Contains(user?.Role)) { return new PagedItems(); } @@ -45,15 +45,19 @@ public async Task> GetPluginMenu() new PluginMenuDef("System", weight: 30) { IsHeader = true, - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } }, new PluginMenuDef("Plugins", link: "page/plugin", icon: "bx bx-plug", weight: 31) { - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } }, new PluginMenuDef("Settings", link: "page/setting", icon: "bx bx-cog", weight: 32) { - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } + }, + new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 33) + { + Roles = new List { UserRole.Root, UserRole.Admin } } }; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index de31ef6be..90bf20f17 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Users.Settings; +using BotSharp.Abstraction.Users.Enums; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using System.ComponentModel.DataAnnotations; @@ -11,11 +12,18 @@ public class UserController : ControllerBase { private readonly IServiceProvider _services; private readonly IUserService _userService; + private readonly IUserIdentity _user; private readonly AccountSetting _setting; - public UserController(IUserService userService, IServiceProvider services, AccountSetting setting) + + public UserController( + IUserService userService, + IServiceProvider services, + IUserIdentity user, + AccountSetting setting) { _services = services; _userService = userService; + _user = user; _setting = setting; } @@ -169,6 +177,46 @@ public async Task UpdateUsersIsDisable([FromQuery] List userIds, [ return await _userService.UpdateUsersIsDisable(userIds, isDisable); } + #region User management + [HttpPost("/users")] + public async Task> GetUsers([FromBody] UserFilter filter) + { + var userService = _services.GetRequiredService(); + var user = await userService.GetUser(_user.Id); + if (user == null || !UserConstant.AdminRoles.Contains(user.Role)) + { + return new PagedItems(); + } + + var users = await userService.GetUsers(filter); + var views = users.Items.Select(x => UserViewModel.FromUser(x)).ToList(); + + return new PagedItems + { + Count = users.Count, + Items = views + }; + } + + + [HttpPut("/user")] + public async Task UpdateUser([FromBody] UserUpdateModel model) + { + if (model == null) return false; + + var userService = _services.GetRequiredService(); + var user = await userService.GetUser(_user.Id); + if (user == null || !UserConstant.AdminRoles.Contains(user.Role)) + { + return false; + } + + var updated = await userService.UpdateUser(UserUpdateModel.ToUser(model), isUpdateUserAgents: true); + return updated; + } + #endregion + + #region Avatar [HttpPost("/user/avatar")] public bool UploadUserAvatar([FromBody] UserAvatarModel input) diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs index 5c23c4d4e..b368cde0f 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs @@ -47,6 +47,7 @@ public class AgentViewModel public PluginDef Plugin { get; set; } public bool Editable { get; set; } + public bool Chatable { get; set; } [JsonPropertyName("created_datetime")] public DateTime CreatedDateTime { get; set; } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs new file mode 100644 index 000000000..43c9fc4d2 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserAgentActionViewModel.cs @@ -0,0 +1,40 @@ +using BotSharp.Abstraction.Agents.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Users; + +public class UserAgentActionViewModel +{ + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonPropertyName("agent")] + public Agent? Agent { get; set; } + + [JsonPropertyName("actions")] + public IEnumerable Actions { get; set; } = []; + + public static UserAgentActionViewModel ToViewModel(UserAgentAction action) + { + return new UserAgentActionViewModel + { + Id = action.Id, + AgentId = action.AgentId, + Agent = action.Agent, + Actions = action.Actions + }; + } + + public static UserAgentAction ToDomainModel(UserAgentActionViewModel action) + { + return new UserAgentAction + { + Id = action.Id, + AgentId = action.AgentId, + Actions = action.Actions + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserUpdateModel.cs new file mode 100644 index 000000000..76b8e347b --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserUpdateModel.cs @@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Users; + +public class UserUpdateModel +{ + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("user_name")] + public string UserName { get; set; } = string.Empty; + + [JsonPropertyName("first_name")] + public string FirstName { get; set; } = string.Empty; + + [JsonPropertyName("last_name")] + public string? LastName { get; set; } + public string? Email { get; set; } + public string? Phone { get; set; } + public string? Type { get; set; } + public string? Role { get; set; } + public string? Source { get; set; } + + [JsonPropertyName("external_id")] + public string? ExternalId { get; set; } + + public IEnumerable Permissions { get; set; } = []; + + [JsonPropertyName("agent_actions")] + public IEnumerable AgentActions { get; set; } = []; + + public static User ToUser(UserUpdateModel model) + { + return new User + { + Id = model.Id, + UserName = model.UserName, + FirstName = model.FirstName, + LastName = model.LastName, + Email = model.Email, + Phone = model.Phone, + Type = model.Type, + Role = model.Role, + Source = model.Source, + ExternalId = model.ExternalId, + Permissions = model.Permissions, + AgentActions = model.AgentActions?.Select(x => UserAgentActionViewModel.ToDomainModel(x)) ?? [] + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs index 5e9a17f06..4df12f8cf 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs @@ -16,14 +16,23 @@ public class UserViewModel public string? Phone { get; set; } public string Type { get; set; } = UserType.Client; public string Role { get; set; } = UserRole.User; + [JsonPropertyName("full_name")] public string FullName => $"{FirstName} {LastName}".Trim(); public string? Source { get; set; } + [JsonPropertyName("external_id")] public string? ExternalId { get; set; } public string Avatar { get; set; } = "/user/avatar"; + + public IEnumerable Permissions { get; set; } = []; + + [JsonPropertyName("agent_actions")] + public IEnumerable AgentActions { get; set; } = []; + [JsonPropertyName("create_date")] public DateTime CreateDate { get; set; } + [JsonPropertyName("update_date")] public DateTime UpdateDate { get; set; } @@ -54,6 +63,8 @@ public static UserViewModel FromUser(User user) Role = user.Role, Source = user.Source, ExternalId = user.ExternalId, + Permissions = user.Permissions, + AgentActions = user.AgentActions?.Select(x => UserAgentActionViewModel.ToViewModel(x)) ?? [], CreateDate = user.CreatedTime, UpdateDate = user.UpdatedTime, Avatar = "/user/avatar", diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserAgentDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserAgentDocument.cs index bb4af1ae1..9de5345f2 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserAgentDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserAgentDocument.cs @@ -4,8 +4,7 @@ public class UserAgentDocument : MongoBase { public string UserId { get; set; } public string AgentId { get; set; } - public bool Editable { get; set; } - + public IEnumerable Actions { get; set; } = []; public DateTime CreatedTime { get; set; } public DateTime UpdatedTime { get; set; } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs index c6f42eab2..4054d09dd 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Collections/UserDocument.cs @@ -22,6 +22,7 @@ public class UserDocument : MongoBase public string? AffiliateId { get; set; } public string? EmployeeId { get; set; } public bool IsDisabled { get; set; } + public IEnumerable Permissions { get; set; } = []; public DateTime CreatedTime { get; set; } public DateTime UpdatedTime { get; set; } @@ -47,6 +48,7 @@ public User ToUser() VerificationCode = VerificationCode, Verified = Verified, RegionCode = RegionCode, + Permissions = Permissions, }; } } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs index 7d47a98cb..755958c7b 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/MongoStoragePlugin.cs @@ -37,7 +37,7 @@ public bool AttachMenu(List menu) var section = menu.First(x => x.Label == "Apps"); menu.Add(new PluginMenuDef("MongoDB", icon: "bx bx-data", link: "page/mongodb", weight: section.Weight + 10) { - Roles = new List { UserRole.Admin } + Roles = new List { UserRole.Root, UserRole.Admin } }); return true; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 49c8616e9..d4a02e932 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -318,19 +318,36 @@ public List GetAgents(AgentFilter filter) return agentDocs.Select(x => TransformAgentDocument(x)).ToList(); } - public List GetAgentsByUser(string userId) + public List GetUserAgents(string userId) { - var agentIds = (from ua in _dc.UserAgents.AsQueryable() - join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id - where ua.UserId == userId || u.ExternalId == userId - select ua.AgentId).ToList(); + var found = (from ua in _dc.UserAgents.AsQueryable() + join u in _dc.Users.AsQueryable() on ua.UserId equals u.Id + where ua.UserId == userId || u.ExternalId == userId + select ua).ToList(); - var filter = new AgentFilter + if (found.IsNullOrEmpty()) return []; + + var agentIds = found.Select(x => x.AgentId).Distinct().ToList(); + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + var res = found.Select(x => new UserAgent { - AgentIds = agentIds - }; - var agents = GetAgents(filter); - return agents; + Id = x.Id, + UserId = x.UserId, + AgentId = x.AgentId, + Actions = x.Actions, + CreatedTime = x.CreatedTime, + UpdatedTime = x.UpdatedTime + }).ToList(); + + foreach (var item in res) + { + var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (agent == null) continue; + + item.Agent = agent; + } + + return res; } public List GetAgentResponses(string agentId, string prefix, string intent) @@ -415,9 +432,9 @@ public void BulkInsertUserAgents(List userAgents) var userAgentDocs = userAgents.Select(x => new UserAgentDocument { Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - AgentId = x.AgentId, UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty, - Editable = x.Editable, + AgentId = x.AgentId, + Actions = x.Actions, CreatedTime = x.CreatedTime, UpdatedTime = x.UpdatedTime }).ToList(); @@ -446,11 +463,11 @@ public bool DeleteAgent(string agentId) if (string.IsNullOrEmpty(agentId)) return false; var agentFilter = Builders.Filter.Eq(x => x.Id, agentId); - var agentUserFilter = Builders.Filter.Eq(x => x.AgentId, agentId); + var userAgentFilter = Builders.Filter.Eq(x => x.AgentId, agentId); var agentTaskFilter = Builders.Filter.Eq(x => x.AgentId, agentId); _dc.Agents.DeleteOne(agentFilter); - _dc.UserAgents.DeleteMany(agentUserFilter); + _dc.UserAgents.DeleteMany(userAgentFilter); _dc.AgentTasks.DeleteMany(agentTaskFilter); return true; } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Transaction.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Transaction.cs deleted file mode 100644 index e2ffbb0e8..000000000 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Transaction.cs +++ /dev/null @@ -1,157 +0,0 @@ -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Users.Models; - -namespace BotSharp.Plugin.MongoStorage.Repository; - -public partial class MongoRepository -{ - public void Add(object entity) - { - if (entity is Agent agent) - { - _agents.Add(agent); - _changedTableNames.Add(nameof(Agent)); - } - else if (entity is User user) - { - _users.Add(user); - _changedTableNames.Add(nameof(User)); - } - else if (entity is UserAgent userAgent) - { - _userAgents.Add(userAgent); - _changedTableNames.Add(nameof(UserAgent)); - } - } - - public int Transaction(Action action) - { - _changedTableNames.Clear(); - action(); - - foreach (var table in _changedTableNames) - { - if (table == nameof(Agent)) - { - var agents = _agents.Select(x => new AgentDocument - { - Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - Name = x.Name, - IconUrl = x.IconUrl, - Description = x.Description, - Instruction = x.Instruction, - ChannelInstructions = x.ChannelInstructions? - .Select(i => ChannelInstructionMongoElement.ToMongoElement(i))? - .ToList() ?? new List(), - Templates = x.Templates? - .Select(t => AgentTemplateMongoElement.ToMongoElement(t))? - .ToList() ?? new List(), - Functions = x.Functions? - .Select(f => FunctionDefMongoElement.ToMongoElement(f))? - .ToList() ?? new List(), - Responses = x.Responses? - .Select(r => AgentResponseMongoElement.ToMongoElement(r))? - .ToList() ?? new List(), - Samples = x.Samples ?? new List(), - Utilities = x.Utilities ?? new List(), - IsPublic = x.IsPublic, - Type = x.Type, - InheritAgentId = x.InheritAgentId, - Disabled = x.Disabled, - Profiles = x.Profiles, - RoutingRules = x.RoutingRules? - .Select(r => RoutingRuleMongoElement.ToMongoElement(r))? - .ToList() ?? new List(), - LlmConfig = AgentLlmConfigMongoElement.ToMongoElement(x.LlmConfig), - CreatedTime = x.CreatedDateTime, - UpdatedTime = x.UpdatedDateTime - }).ToList(); - - foreach (var agent in agents) - { - var filter = Builders.Filter.Eq(x => x.Id, agent.Id); - var update = Builders.Update - .Set(x => x.Name, agent.Name) - .Set(x => x.Description, agent.Description) - .Set(x => x.Instruction, agent.Instruction) - .Set(x => x.ChannelInstructions, agent.ChannelInstructions) - .Set(x => x.Templates, agent.Templates) - .Set(x => x.Functions, agent.Functions) - .Set(x => x.Responses, agent.Responses) - .Set(x => x.Samples, agent.Samples) - .Set(x => x.Utilities, agent.Utilities) - .Set(x => x.IsPublic, agent.IsPublic) - .Set(x => x.Type, agent.Type) - .Set(x => x.InheritAgentId, agent.InheritAgentId) - .Set(x => x.Disabled, agent.Disabled) - .Set(x => x.Profiles, agent.Profiles) - .Set(x => x.RoutingRules, agent.RoutingRules) - .Set(x => x.LlmConfig, agent.LlmConfig) - .Set(x => x.CreatedTime, agent.CreatedTime) - .Set(x => x.UpdatedTime, agent.UpdatedTime); - _dc.Agents.UpdateOne(filter, update, _options); - } - } - else if (table == nameof(User)) - { - var users = _users.Select(x => new UserDocument - { - Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - UserName = x.UserName, - FirstName = x.FirstName, - LastName = x.LastName, - Salt = x.Salt, - Password = x.Password, - Email = x.Email, - ExternalId = x.ExternalId, - Role = x.Role, - CreatedTime = x.CreatedTime, - UpdatedTime = x.UpdatedTime - }).ToList(); - - foreach (var user in users) - { - var filter = Builders.Filter.Eq(x => x.Id, user.Id); - var update = Builders.Update - .Set(x => x.UserName, user.UserName) - .Set(x => x.FirstName, user.FirstName) - .Set(x => x.LastName, user.LastName) - .Set(x => x.Email, user.Email) - .Set(x => x.Salt, user.Salt) - .Set(x => x.Password, user.Password) - .Set(x => x.ExternalId, user.ExternalId) - .Set(x => x.Role, user.Role) - .Set(x => x.CreatedTime, user.CreatedTime) - .Set(x => x.UpdatedTime, user.UpdatedTime); - _dc.Users.UpdateOne(filter, update, _options); - } - } - else if (table == nameof(UserAgent)) - { - var userAgents = _userAgents.Select(x => new UserAgentDocument - { - Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), - AgentId = x.AgentId, - UserId = !string.IsNullOrEmpty(x.UserId) ? x.UserId : string.Empty, - Editable = x.Editable, - CreatedTime = x.CreatedTime, - UpdatedTime = x.UpdatedTime - }).ToList(); - - foreach (var userAgent in userAgents) - { - var filter = Builders.Filter.Eq(x => x.Id, userAgent.Id); - var update = Builders.Update - .Set(x => x.AgentId, userAgent.AgentId) - .Set(x => x.UserId, userAgent.UserId) - .Set(x => x.Editable, userAgent.Editable) - .Set(x => x.CreatedTime, userAgent.CreatedTime) - .Set(x => x.UpdatedTime, userAgent.UpdatedTime); - _dc.UserAgents.UpdateOne(filter, update, _options); - } - } - } - - return _changedTableNames.Count; - } -} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index f5dbc508d..19d933a91 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; @@ -168,4 +170,134 @@ public void UpdateUsersIsDisable(List userIds, bool isDisable) UpdateUserIsDisable(userId, isDisable); } } + + public PagedItems GetUsers(UserFilter filter) + { + var userBuilder = Builders.Filter; + var userFilters = new List>() { userBuilder.Empty }; + + // Apply filters + if (!filter.UserIds.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.Id, filter.UserIds)); + } + if (!filter.UserNames.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.UserName, filter.UserNames)); + } + if (!filter.ExternalIds.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.ExternalId, filter.ExternalIds)); + } + if (!filter.Roles.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.Role, filter.Roles)); + } + if (!filter.Sources.IsNullOrEmpty()) + { + userFilters.Add(userBuilder.In(x => x.Source, filter.Sources)); + } + + // Filter def and sort + var filterDef = userBuilder.And(userFilters); + var sortDef = Builders.Sort.Descending(x => x.CreatedTime); + + // Search + var userDocs = _dc.Users.Find(filterDef).Sort(sortDef).Skip(filter.Offset).Limit(filter.Size).ToList(); + var count = _dc.Users.CountDocuments(filterDef); + + var users = userDocs.Select(x => x.ToUser()).ToList(); + var userIds = users.Select(x => x.Id).ToList(); + var userAgents = _dc.UserAgents.AsQueryable().Where(x => userIds.Contains(x.UserId)).Select(x => new UserAgent + { + Id = x.Id, + UserId = x.UserId, + AgentId = x.AgentId, + Actions = x.Actions ?? Enumerable.Empty(), + CreatedTime = x.CreatedTime, + UpdatedTime = x.UpdatedTime + }).ToList(); + var agentIds = userAgents.Select(x => x.AgentId).Distinct().ToList(); + + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + foreach (var item in userAgents) + { + var agent = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (agent == null) continue; + + item.Agent = agent; + } + + foreach (var user in users) + { + var found = userAgents.Where(x => x.UserId == user.Id).ToList(); + if (found.IsNullOrEmpty()) continue; + + user.AgentActions = found.Select(x => new UserAgentAction + { + Id = x.Id, + AgentId = x.AgentId, + Agent = x.Agent, + Actions = x.Actions + }); + } + } + + return new PagedItems + { + Items = users, + Count = (int)count + }; + } + + + public bool UpdateUser(User user, bool isUpdateUserAgents = false) + { + if (string.IsNullOrEmpty(user?.Id)) return false; + + var userFilter = Builders.Filter.Eq(x => x.Id, user.Id); + var userUpdate = Builders.Update + .Set(x => x.Type, user.Type) + .Set(x => x.Role, user.Role) + .Set(x => x.Permissions, user.Permissions) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.Users.UpdateOne(userFilter, userUpdate); + + if (isUpdateUserAgents) + { + var userAgentDocs = user.AgentActions?.Select(x => new UserAgentDocument + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + UserId = user.Id, + AgentId = x.AgentId, + Actions = x.Actions, + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + })?.ToList() ?? []; + + var toDelete = _dc.UserAgents.Find(Builders.Filter.And( + Builders.Filter.Eq(x => x.UserId, user.Id), + Builders.Filter.Nin(x => x.Id, userAgentDocs.Select(x => x.Id)) + )).ToList(); + + _dc.UserAgents.DeleteMany(Builders.Filter.In(x => x.Id, toDelete.Select(x => x.Id))); + foreach (var doc in userAgentDocs) + { + var userAgentFilter = Builders.Filter.Eq(x => x.Id, doc.Id); + var userAgentUpdate = Builders.Update + .Set(x => x.Id, doc.Id) + .Set(x => x.UserId, user.Id) + .Set(x => x.AgentId, doc.AgentId) + .Set(x => x.Actions, doc.Actions) + .Set(x => x.UpdatedTime, DateTime.UtcNow); + + _dc.UserAgents.UpdateOne(userAgentFilter, userAgentUpdate, _options); + } + } + + return true; + } } diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs index 133edba14..689c06be3 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.cs @@ -1,6 +1,3 @@ -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.Users.Models; using Microsoft.Extensions.Logging; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -25,10 +22,4 @@ public MongoRepository( IsUpsert = true, }; } - - private List _agents = new List(); - private List _users = new List(); - private List _userAgents = new List(); - private List _conversations = new List(); - List _changedTableNames = new List(); }