diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs index fb2bfed2..7de61b76 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentFilter.cs @@ -9,4 +9,9 @@ public class AgentFilter public string? Type { get; set; } public bool? IsPublic { get; set; } public List? AgentIds { get; set; } + + public static AgentFilter Empty() + { + return new AgentFilter(); + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs index 2d8ba477..46ab4621 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/AgentTaskFilter.cs @@ -5,4 +5,9 @@ public class AgentTaskFilter public Pagination Pager { get; set; } = new Pagination(); public string? AgentId { get; set; } public bool? Enabled { get; set; } + + public static AgentTaskFilter Empty() + { + return new AgentTaskFilter(); + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs index 6ebedc5b..ab1a0a9f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs @@ -25,4 +25,9 @@ public class ConversationFilter public IEnumerable? States { get; set; } = []; public IEnumerable? Tags { get; set; } = []; + + public static ConversationFilter Empty() + { + return new ConversationFilter(); + } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs new file mode 100644 index 00000000..0e9e131c --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/RoleFilter.cs @@ -0,0 +1,12 @@ +namespace BotSharp.Abstraction.Repositories.Filters; + +public class RoleFilter +{ + [JsonPropertyName("names")] + public IEnumerable? Names { get; set; } + + public static RoleFilter Empty() + { + return new RoleFilter(); + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs similarity index 78% rename from src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs rename to src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs index 73e1794d..6ac35ed2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/UserFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/UserFilter.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Abstraction.Users.Models; +namespace BotSharp.Abstraction.Repositories.Filters; public class UserFilter : Pagination { @@ -16,4 +16,9 @@ public class UserFilter : Pagination [JsonPropertyName("sources")] public IEnumerable? Sources { get; set; } + + public static UserFilter Empty() + { + return new UserFilter(); + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs index 57900aa0..910b10c9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Loggers.Models; using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Roles.Models; using BotSharp.Abstraction.Shared; using BotSharp.Abstraction.Tasks.Models; using BotSharp.Abstraction.Translation.Models; @@ -16,6 +17,12 @@ public interface IBotSharpRepository : IHaveServiceProvider void SavePluginConfig(PluginConfig config); #endregion + #region Role + IEnumerable GetRoles(RoleFilter filter) => throw new NotImplementedException(); + Role? GetRoleDetails(string roleId) => throw new NotImplementedException(); + bool UpdateRole(Role role, bool isUpdateRoleAgents = false) => throw new NotImplementedException(); + #endregion + #region User User? GetUserByEmail(string email) => throw new NotImplementedException(); User? GetUserByPhone(string phone) => throw new NotImplementedException(); @@ -34,6 +41,7 @@ public interface IBotSharpRepository : IHaveServiceProvider 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(); + User? GetUserDetails(string userId) => throw new NotImplementedException(); bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException(); #endregion diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs new file mode 100644 index 00000000..12e45dfd --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/IRoleService.cs @@ -0,0 +1,12 @@ +using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Roles.Models; + +namespace BotSharp.Abstraction.Roles; + +public interface IRoleService +{ + Task> GetRoleOptions(); + Task> GetRoles(RoleFilter filter); + Task GetRoleDetails(string roleId); + Task UpdateRole(Role role, bool isUpdateRoleAgents = false); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs new file mode 100644 index 00000000..a11029f5 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/Role.cs @@ -0,0 +1,22 @@ +namespace BotSharp.Abstraction.Roles.Models; + +public class Role +{ + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("permissions")] + public IEnumerable Permissions { get; set; } = []; + + [JsonIgnore] + public IEnumerable AgentActions { 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/Roles/Models/RoleAgent.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs new file mode 100644 index 00000000..430a89a1 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgent.cs @@ -0,0 +1,25 @@ +namespace BotSharp.Abstraction.Roles.Models; + +public class RoleAgent +{ + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("user_id")] + public string RoleId { get; set; } = string.Empty; + + [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/Roles/Models/RoleAgentAction.cs b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs new file mode 100644 index 00000000..25cc0685 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Roles/Models/RoleAgentAction.cs @@ -0,0 +1,16 @@ +namespace BotSharp.Abstraction.Roles.Models; + +public class RoleAgentAction +{ + [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/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs index 2a869d02..77e40418 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users.Models; using BotSharp.OpenAPI.ViewModels.Users; @@ -7,7 +8,8 @@ public interface IUserService { Task GetUser(string id); Task> GetUsers(UserFilter filter); - Task UpdateUser(User model, bool isUpdateUserAgents = false); + Task GetUserDetails(string userId); + Task UpdateUser(User user, bool isUpdateUserAgents = false); Task CreateUser(User user); Task ActiveUser(UserActivationModel model); Task GetAffiliateToken(string authorization); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index 59da45a5..dadc3532 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -1,7 +1,4 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Routing.Models; -using BotSharp.Abstraction.Users.Models; -using Microsoft.Extensions.Logging; using System.IO; namespace BotSharp.Core.Repository @@ -358,6 +355,11 @@ namespace BotSharp.Core.Repository public List GetAgents(AgentFilter filter) { + if (filter == null) + { + filter = AgentFilter.Empty(); + } + var query = Agents; if (!string.IsNullOrEmpty(filter.AgentName)) { diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs index 776cf137..f855544b 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.AgentTask.cs @@ -8,6 +8,11 @@ public partial class FileRepository #region Task public PagedItems GetAgentTasks(AgentTaskFilter filter) { + if (filter == null) + { + filter = AgentTaskFilter.Empty(); + } + var tasks = new List(); var pager = filter.Pager ?? new Pagination(); var skipCount = 0; diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 28d0a6cc..9f5ed57f 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -322,6 +322,11 @@ namespace BotSharp.Core.Repository public PagedItems GetConversations(ConversationFilter filter) { + if (filter == null) + { + filter = ConversationFilter.Empty(); + } + var records = new List(); var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir); var pager = filter?.Pager ?? new Pagination(); diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs new file mode 100644 index 00000000..13036a91 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Role.cs @@ -0,0 +1,97 @@ +using BotSharp.Abstraction.Users.Models; +using System.IO; + +namespace BotSharp.Core.Repository; + +public partial class FileRepository +{ + public IEnumerable GetRoles(RoleFilter filter) + { + var roles = Roles; + if (filter == null) + { + filter = RoleFilter.Empty(); + } + + // Apply filters + if (!filter.Names.IsNullOrEmpty()) + { + roles = roles.Where(x => filter.Names.Contains(x.Id)); + } + + return roles.ToList(); + } + + public Role? GetRoleDetails(string roleId) + { + if (string.IsNullOrWhiteSpace(roleId)) return null; + + var role = Roles.FirstOrDefault(x => x.Id == roleId); + if (role == null) return null; + + var agentActions = new List(); + var roleAgents = RoleAgents?.Where(x => x.RoleId == roleId)?.ToList() ?? []; + var agentIds = roleAgents.Select(x => x.AgentId).Distinct().ToList(); + + if (!agentIds.IsNullOrEmpty()) + { + var agents = GetAgents(new AgentFilter { AgentIds = agentIds }); + + foreach (var item in roleAgents) + { + var found = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (found == null) continue; + + agentActions.Add(new RoleAgentAction + { + Id = item.Id, + AgentId = found.Id, + Agent = found, + Actions = item.Actions + }); + } + } + + role.AgentActions = agentActions; + return role; + } + + public bool UpdateRole(Role role, bool isUpdateRoleAgents = false) + { + if (string.IsNullOrEmpty(role?.Id) || string.IsNullOrEmpty(role?.Name)) + { + return false; + } + + var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER, role.Id); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + var roleFile = Path.Combine(dir, ROLE_FILE); + role.CreatedTime = DateTime.UtcNow; + role.UpdatedTime = DateTime.UtcNow; + File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options)); + + if (isUpdateRoleAgents) + { + var roleAgents = role.AgentActions?.Select(x => new RoleAgent + { + Id = !string.IsNullOrEmpty(x.Id) ? x.Id : Guid.NewGuid().ToString(), + RoleId = role.Id, + AgentId = x.AgentId, + Actions = x.Actions ?? [], + CreatedTime = DateTime.UtcNow, + UpdatedTime = DateTime.UtcNow + })?.ToList() ?? []; + + var roleAgentFile = Path.Combine(dir, ROLE_AGENT_FILE); + File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options)); + _roleAgents = []; + } + + _roles = []; + return true; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs index 80ccacc4..c55f248c 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs @@ -73,6 +73,11 @@ public partial class FileRepository public PagedItems GetUsers(UserFilter filter) { + if (filter == null) + { + filter = UserFilter.Empty(); + } + var users = Users; // Apply filters @@ -97,34 +102,6 @@ public partial class FileRepository 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), @@ -132,6 +109,40 @@ public partial class FileRepository }; } + public User? GetUserDetails(string userId) + { + if (string.IsNullOrWhiteSpace(userId)) return null; + + var user = Users.FirstOrDefault(x => x.Id == userId); + if (user == null) return null; + + var agentActions = new List(); + var userAgents = UserAgents?.Where(x => x.UserId == 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) + { + var found = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (found == null) continue; + + agentActions.Add(new UserAgentAction + { + Id = item.Id, + AgentId = found.Id, + Agent = found, + Actions = item.Actions ?? [] + }); + } + } + + user.AgentActions = agentActions; + return user; + } + public bool UpdateUser(User user, bool isUpdateUserAgents = false) { if (string.IsNullOrEmpty(user?.Id)) return false; @@ -160,10 +171,10 @@ public partial class FileRepository var userAgentFile = Path.Combine(dir, USER_AGENT_FILE); File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options)); + _userAgents = []; } _users = []; - _userAgents = []; return true; } } diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs index f3e1fddf..0edcb699 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs @@ -22,30 +22,38 @@ public partial class FileRepository : IBotSharpRepository private const string AGENT_FILE = "agent.json"; private const string AGENT_INSTRUCTION_FILE = "instruction"; private const string AGENT_SAMPLES_FILE = "samples.txt"; - private const string USER_FILE = "user.json"; - private const string USER_AGENT_FILE = "agents.json"; - private const string CONVERSATION_FILE = "conversation.json"; - private const string STATS_FILE = "stats.json"; - private const string DIALOG_FILE = "dialogs.json"; - private const string STATE_FILE = "state.json"; - private const string BREAKPOINT_FILE = "breakpoint.json"; - private const string EXECUTION_LOG_FILE = "execution.log"; - private const string PLUGIN_CONFIG_FILE = "config.json"; - private const string AGENT_TASK_PREFIX = "#metadata"; - private const string AGENT_TASK_SUFFIX = "/metadata"; - private const string TRANSLATION_MEMORY_FILE = "memory.json"; private const string AGENT_INSTRUCTIONS_FOLDER = "instructions"; private const string AGENT_FUNCTIONS_FOLDER = "functions"; private const string AGENT_TEMPLATES_FOLDER = "templates"; private const string AGENT_RESPONSES_FOLDER = "responses"; private const string AGENT_TASKS_FOLDER = "tasks"; + private const string AGENT_TASK_PREFIX = "#metadata"; + private const string AGENT_TASK_SUFFIX = "/metadata"; + + private const string CONVERSATION_FILE = "conversation.json"; + private const string DIALOG_FILE = "dialogs.json"; + private const string STATE_FILE = "state.json"; + private const string BREAKPOINT_FILE = "breakpoint.json"; + private const string TRANSLATION_MEMORY_FILE = "memory.json"; + private const string USERS_FOLDER = "users"; + private const string USER_FILE = "user.json"; + private const string USER_AGENT_FILE = "agents.json"; + + private const string ROLES_FOLDER = "roles"; + private const string ROLE_FILE = "role.json"; + private const string ROLE_AGENT_FILE = "agents.json"; + private const string KNOWLEDGE_FOLDER = "knowledgebase"; private const string VECTOR_FOLDER = "vector"; private const string COLLECTION_CONFIG_FILE = "collection-config.json"; private const string KNOWLEDGE_DOC_FOLDER = "document"; private const string KNOWLEDGE_DOC_META_FILE = "meta.json"; + private const string EXECUTION_LOG_FILE = "execution.log"; + private const string PLUGIN_CONFIG_FILE = "config.json"; + private const string STATS_FILE = "stats.json"; + public FileRepository( IServiceProvider services, BotSharpDatabaseSettings dbSettings, @@ -73,12 +81,68 @@ public partial class FileRepository : IBotSharpRepository _dbSettings.FileRepository = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _dbSettings.FileRepository); } + private List _roles = new List(); private List _users = new List(); private List _agents = new List(); + private List _roleAgents = new List(); private List _userAgents = new List(); private List _conversations = new List(); private PluginConfig? _pluginConfig = null; + private IQueryable Roles + { + get + { + if (!_roles.IsNullOrEmpty()) + { + return _roles.AsQueryable(); + } + + var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); + _roles = new List(); + if (Directory.Exists(dir)) + { + foreach (var d in Directory.GetDirectories(dir)) + { + var roleFile = Path.Combine(d, ROLE_FILE); + if (!Directory.Exists(d) || !File.Exists(roleFile)) + continue; + + var json = File.ReadAllText(roleFile); + _roles.Add(JsonSerializer.Deserialize(json, _options)); + } + } + return _roles.AsQueryable(); + } + } + + private IQueryable RoleAgents + { + get + { + if (!_roleAgents.IsNullOrEmpty()) + { + return _roleAgents.AsQueryable(); + } + + var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER); + _roleAgents = new List(); + if (Directory.Exists(dir)) + { + foreach (var d in Directory.GetDirectories(dir)) + { + var file = Path.Combine(d, ROLE_AGENT_FILE); + if (!Directory.Exists(d) || !File.Exists(file)) + continue; + + var json = File.ReadAllText(file); + _roleAgents.AddRange(JsonSerializer.Deserialize>(json, _options)); + } + } + return _roleAgents.AsQueryable(); + } + } + private IQueryable Users { get diff --git a/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs new file mode 100644 index 00000000..b9f04cfe --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Roles/Services/RoleService.cs @@ -0,0 +1,56 @@ +using BotSharp.Abstraction.Users.Enums; +using System.Reflection; + +namespace BotSharp.Core.Roles.Services; + +public class RoleService : IRoleService +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public RoleService( + IServiceProvider services, + ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task> GetRoleOptions() + { + var fields = typeof(UserRole).GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(x => x.IsLiteral && !x.IsInitOnly).ToList(); + + return fields.Select(x => x.GetValue(null)?.ToString()) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct() + .ToList(); + } + + public async Task> GetRoles(RoleFilter filter) + { + var db = _services.GetRequiredService(); + var roles = db.GetRoles(filter); + return roles; + } + + public async Task GetRoleDetails(string roleId) + { + var db = _services.GetRequiredService(); + var role = db.GetRoleDetails(roleId); + return role; + } + + public async Task UpdateRole(Role role, bool isUpdateRoleAgents = false) + { + if (role == null) return false; + + if (string.IsNullOrEmpty(role.Id)) + { + role.Id = Guid.NewGuid().ToString(); + } + + var db = _services.GetRequiredService(); + return db.UpdateRole(role, isUpdateRoleAgents); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs index daf8a340..9fa2b73a 100644 --- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs +++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs @@ -407,10 +407,18 @@ public class UserService : IUserService return users; } - public async Task UpdateUser(User model, bool isUpdateUserAgents = false) + public async Task GetUserDetails(string userId) { var db = _services.GetRequiredService(); - return db.UpdateUser(model, isUpdateUserAgents); + return db.GetUserDetails(userId); + } + + public async Task UpdateUser(User user, bool isUpdateUserAgents = false) + { + if (user == null) return false; + + var db = _services.GetRequiredService(); + return db.UpdateUser(user, isUpdateUserAgents); } public async Task ActiveUser(UserActivationModel model) diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 8a0ca2af..c486a042 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -16,6 +16,8 @@ global using BotSharp.Abstraction.Agents; global using BotSharp.Abstraction.Conversations; global using BotSharp.Abstraction.Knowledges; global using BotSharp.Abstraction.Users; +global using BotSharp.Abstraction.Roles; +global using BotSharp.Abstraction.Roles.Models; global using BotSharp.Abstraction.Utilities; global using BotSharp.Abstraction.Conversations.Models; global using BotSharp.Abstraction.Agents.Settings; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs new file mode 100644 index 00000000..5eedc928 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/RoleController.cs @@ -0,0 +1,64 @@ +using BotSharp.Abstraction.Roles; +using BotSharp.Abstraction.Users.Enums; + +namespace BotSharp.OpenAPI.Controllers; + +[Authorize] +[ApiController] +public class RoleController : ControllerBase +{ + private readonly IServiceProvider _services; + private readonly IRoleService _roleService; + private readonly IUserIdentity _user; + + public RoleController( + IServiceProvider services, + IRoleService roleService, + IUserIdentity user) + { + _services = services; + _roleService = roleService; + _user = user; + } + + [HttpGet("/role/options")] + public async Task> GetRoleOptions() + { + return await _roleService.GetRoleOptions(); + } + + [HttpPost("/roles")] + public async Task> GetRoles([FromBody] RoleFilter? filter = null) + { + if (filter == null) + { + filter = RoleFilter.Empty(); + } + + var roles = await _roleService.GetRoles(filter); + return roles.Select(x => RoleViewModel.FromRole(x)).ToList(); + } + + [HttpGet("/role/{id}/details")] + public async Task GetRoleDetails([FromRoute] string id) + { + var role = await _roleService.GetRoleDetails(id); + return RoleViewModel.FromRole(role); + } + + [HttpPut("/role")] + public async Task UpdateRole([FromBody] RoleUpdateModel 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 role = RoleUpdateModel.ToRole(model); + return await _roleService.UpdateRole(role, isUpdateRoleAgents: true); + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs index 90bf20f1..366bb91b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs @@ -198,6 +198,13 @@ public class UserController : ControllerBase }; } + [HttpGet("/user/{id}/details")] + public async Task GetUserDetails(string id) + { + var userService = _services.GetRequiredService(); + var user = await userService.GetUserDetails(id); + return UserViewModel.FromUser(user); + } [HttpPut("/user")] public async Task UpdateUser([FromBody] UserUpdateModel model) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Using.cs b/src/Infrastructure/BotSharp.OpenAPI/Using.cs index 3542cef9..602f4437 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Using.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Using.cs @@ -32,3 +32,4 @@ global using BotSharp.OpenAPI.ViewModels.Conversations; global using BotSharp.OpenAPI.ViewModels.Users; global using BotSharp.OpenAPI.ViewModels.Agents; global using BotSharp.OpenAPI.ViewModels.Files; +global using BotSharp.OpenAPI.ViewModels.Roles; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs new file mode 100644 index 00000000..ffd7987e --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleAgentActionViewModel.cs @@ -0,0 +1,42 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Roles.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Roles; + +public class RoleAgentActionViewModel +{ + [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 RoleAgentActionViewModel ToViewModel(RoleAgentAction action) + { + return new RoleAgentActionViewModel + { + Id = action.Id, + AgentId = action.AgentId, + Agent = action.Agent, + Actions = action.Actions + }; + } + + public static RoleAgentAction ToDomainModel(RoleAgentActionViewModel action) + { + return new RoleAgentAction + { + Id = action.Id, + AgentId = action.AgentId, + Actions = action.Actions + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs new file mode 100644 index 00000000..eece96fc --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleUpdateModel.cs @@ -0,0 +1,30 @@ +using BotSharp.Abstraction.Roles.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Roles; + +public class RoleUpdateModel +{ + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("permissions")] + public IEnumerable Permissions { get; set; } = []; + + [JsonPropertyName("agent_actions")] + public IEnumerable AgentActions { get; set; } = []; + + public static Role ToRole(RoleUpdateModel model) + { + return new Role + { + Id = model.Id, + Name = model.Name, + Permissions = model.Permissions, + AgentActions = model.AgentActions?.Select(x => RoleAgentActionViewModel.ToDomainModel(x)) ?? [] + }; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs new file mode 100644 index 00000000..e060b482 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Roles/RoleViewModel.cs @@ -0,0 +1,40 @@ +using BotSharp.Abstraction.Roles.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.OpenAPI.ViewModels.Roles; + +public class RoleViewModel +{ + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("permissions")] + 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; } + + public static RoleViewModel FromRole(Role? role) + { + if (role == null) return null; + + return new RoleViewModel + { + Id = role.Id, + Name = role.Name, + Permissions = role.Permissions, + AgentActions = role.AgentActions?.Select(x => RoleAgentActionViewModel.ToViewModel(x)) ?? [], + CreateDate = role.CreatedTime, + UpdateDate = role.UpdatedTime + }; + } +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 44a4e2bc..b0146903 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -283,6 +283,11 @@ public partial class MongoRepository public List GetAgents(AgentFilter filter) { + if (filter == null) + { + filter = AgentFilter.Empty(); + } + var agents = new List(); var builder = Builders.Filter; var filters = new List>() { builder.Empty }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs index a00858c4..2946b7b0 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.AgentTask.cs @@ -8,6 +8,11 @@ public partial class MongoRepository #region Task public PagedItems GetAgentTasks(AgentTaskFilter filter) { + if (filter == null) + { + filter = AgentTaskFilter.Empty(); + } + var pager = filter.Pager ?? new Pagination(); var builder = Builders.Filter; var filters = new List>() { builder.Empty }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index 85fa1033..ccbfc1af 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -281,6 +281,11 @@ public partial class MongoRepository public PagedItems GetConversations(ConversationFilter filter) { + if (filter == null) + { + filter = ConversationFilter.Empty(); + } + var convBuilder = Builders.Filter; var convFilters = new List>() { convBuilder.Empty }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs index 19d933a9..d7115c36 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.User.cs @@ -2,6 +2,8 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users.Enums; using BotSharp.Abstraction.Users.Models; +using MongoDB.Driver; +using System.Globalization; namespace BotSharp.Plugin.MongoStorage.Repository; @@ -173,6 +175,11 @@ public partial class MongoRepository public PagedItems GetUsers(UserFilter filter) { + if (filter == null) + { + filter = UserFilter.Empty(); + } + var userBuilder = Builders.Filter; var userFilters = new List>() { userBuilder.Empty }; @@ -207,44 +214,6 @@ public partial class MongoRepository 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, @@ -252,6 +221,48 @@ public partial class MongoRepository }; } + public User? GetUserDetails(string userId) + { + if (string.IsNullOrWhiteSpace(userId)) return null; + + var userDoc = _dc.Users.Find(Builders.Filter.Eq(x => x.Id, userId)).FirstOrDefault(); + if (userDoc == null) return null; + + var user = userDoc.ToUser(); + + var userAgents = _dc.UserAgents.AsQueryable().Where(x => x.UserId == userId).Select(x => new UserAgent + { + Id = x.Id, + UserId = x.UserId, + AgentId = x.AgentId, + Actions = x.Actions ?? Enumerable.Empty() + }).ToList(); + + var agentActions = new List(); + 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 found = agents.FirstOrDefault(x => x.Id == item.AgentId); + if (found == null) continue; + + agentActions.Add(new UserAgentAction + { + Id = item.Id, + AgentId = found.Id, + Agent = found, + Actions = item.Actions + }); + } + } + + user.AgentActions = agentActions; + return user; + } public bool UpdateUser(User user, bool isUpdateUserAgents = false) {