Merge pull request #749 from iceljc/features/add-role

Features/add role
This commit is contained in:
iceljc 2024-11-14 20:23:59 -06:00 committed by GitHub
commit 5f82152503
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 1148 additions and 163 deletions

View file

@ -9,4 +9,9 @@ public class AgentFilter
public string? Type { get; set; }
public bool? IsPublic { get; set; }
public List<string>? AgentIds { get; set; }
public static AgentFilter Empty()
{
return new AgentFilter();
}
}

View file

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

View file

@ -25,4 +25,9 @@ public class ConversationFilter
public IEnumerable<KeyValue>? States { get; set; } = [];
public IEnumerable<string>? Tags { get; set; } = [];
public static ConversationFilter Empty()
{
return new ConversationFilter();
}
}

View file

@ -0,0 +1,12 @@
namespace BotSharp.Abstraction.Repositories.Filters;
public class RoleFilter
{
[JsonPropertyName("names")]
public IEnumerable<string>? Names { get; set; }
public static RoleFilter Empty()
{
return new RoleFilter();
}
}

View file

@ -1,4 +1,4 @@
namespace BotSharp.Abstraction.Users.Models;
namespace BotSharp.Abstraction.Repositories.Filters;
public class UserFilter : Pagination
{
@ -14,6 +14,14 @@ public class UserFilter : Pagination
[JsonPropertyName("roles")]
public IEnumerable<string>? Roles { get; set; }
[JsonPropertyName("types")]
public IEnumerable<string>? Types { get; set; }
[JsonPropertyName("sources")]
public IEnumerable<string>? Sources { get; set; }
public static UserFilter Empty()
{
return new UserFilter();
}
}

View file

@ -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,13 @@ public interface IBotSharpRepository : IHaveServiceProvider
void SavePluginConfig(PluginConfig config);
#endregion
#region Role
bool RefreshRoles(IEnumerable<Role> roles) => throw new NotImplementedException();
IEnumerable<Role> GetRoles(RoleFilter filter) => throw new NotImplementedException();
Role? GetRoleDetails(string roleId, bool includeAgent = false) => throw new NotImplementedException();
bool UpdateRole(Role role, bool updateRoleAgents = false) => throw new NotImplementedException();
#endregion
#region User
User? GetUserByEmail(string email) => throw new NotImplementedException();
User? GetUserByPhone(string phone) => throw new NotImplementedException();
@ -34,7 +42,8 @@ public interface IBotSharpRepository : IHaveServiceProvider
void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException();
void UpdateUsersIsDisable(List<string> userIds, bool isDisable) => throw new NotImplementedException();
PagedItems<User> GetUsers(UserFilter filter) => throw new NotImplementedException();
bool UpdateUser(User user, bool isUpdateUserAgents = false) => throw new NotImplementedException();
User? GetUserDetails(string userId, bool includeAgent = false) => throw new NotImplementedException();
bool UpdateUser(User user, bool updateUserAgents = false) => throw new NotImplementedException();
#endregion
#region Agent

View file

@ -0,0 +1,13 @@
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Roles.Models;
namespace BotSharp.Abstraction.Roles;
public interface IRoleService
{
Task<bool> RefreshRoles();
Task<IEnumerable<string>> GetRoleOptions();
Task<IEnumerable<Role>> GetRoles(RoleFilter filter);
Task<Role?> GetRoleDetails(string roleId);
Task<bool> UpdateRole(Role role, bool isUpdateRoleAgents = false);
}

View file

@ -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<string> Permissions { get; set; } = [];
[JsonIgnore]
public IEnumerable<RoleAgentAction> AgentActions { get; set; } = [];
[JsonPropertyName("updated_time")]
public DateTime UpdatedTime { get; set; }
[JsonPropertyName("created_time")]
public DateTime CreatedTime { get; set; }
}

View file

@ -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<string> Actions { get; set; } = [];
[JsonIgnore]
public Agent? Agent { get; set; }
[JsonPropertyName("updated_time")]
public DateTime UpdatedTime { get; set; }
[JsonPropertyName("created_time")]
public DateTime CreatedTime { get; set; }
}

View file

@ -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<string> Actions { get; set; } = [];
}

View file

@ -4,4 +4,6 @@ public static class UserAction
{
public const string Edit = "edit";
public const string Chat = "chat";
public const string Train = "train";
public const string Evaluate = "evaluate";
}

View file

@ -2,21 +2,23 @@ namespace BotSharp.Abstraction.Users.Enums;
public class UserRole
{
public const string Root = "root";
/// <summary>
/// Admin account
/// </summary>
public const string Admin = "admin";
/// <summary>
/// Customer service representative (CSR)
/// </summary>
public const string CSR = "csr";
/// <summary>
/// Authorized user
/// </summary>
public const string User = "user";
/// <summary>
/// Customer service representative (CSR)
/// </summary>
public const string CSR = "csr";
/// <summary>
/// Back office operations
/// </summary>
@ -33,6 +35,4 @@ public class UserRole
/// AI Assistant
/// </summary>
public const string Assistant = "assistant";
public const string Root = "root";
}

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Users.Models;
using BotSharp.OpenAPI.ViewModels.Users;
@ -7,7 +8,10 @@ public interface IUserService
{
Task<User> GetUser(string id);
Task<PagedItems<User>> GetUsers(UserFilter filter);
Task<bool> UpdateUser(User model, bool isUpdateUserAgents = false);
Task<User?> GetUserDetails(string userId);
Task<bool> IsAdminUser(string userId);
Task<UserAuthorization> GetUserAuthorizations(IEnumerable<string>? agentIds = null);
Task<bool> UpdateUser(User user, bool isUpdateUserAgents = false);
Task<User> CreateUser(User user);
Task<Token> ActiveUser(UserActivationModel model);
Task<Token?> GetAffiliateToken(string authorization);

View file

@ -0,0 +1,25 @@
namespace BotSharp.Abstraction.Users.Models;
public class UserAuthorization
{
public bool IsAdmin { get; set; }
public IEnumerable<string> Permissions { get; set; } = [];
public IEnumerable<UserAgent> AgentActions { get; set; } = [];
}
public static class UserAuthorizationExtension
{
public static bool IsAgentActionAllowed(this UserAuthorization auth, string agentId, string targetAction)
{
if (auth == null || string.IsNullOrEmpty(agentId)) return false;
if (auth.IsAdmin) return true;
var found = auth.AgentActions.FirstOrDefault(x => x.AgentId == agentId);
if (found == null) return false;
var actions = found.Actions ?? [];
return actions.Any(x => x == targetAction);
}
}

View file

@ -25,8 +25,11 @@ public partial class AgentService
var agentSettings = _services.GetRequiredService<AgentSettings>();
var user = _db.GetUserById(_user.Id);
var userService = _services.GetRequiredService<IUserService>();
var auth = await userService.GetUserAuthorizations();
_db.BulkInsertAgents(new List<Agent> { agentRecord });
if (!UserConstant.AdminRoles.Contains(user.Role))
if (auth.IsAdmin || auth.Permissions.Contains(UserPermission.CreateAgent))
{
_db.BulkInsertUserAgents(new List<UserAgent>
{
@ -34,7 +37,7 @@ public partial class AgentService
{
UserId = user.Id,
AgentId = agentRecord.Id,
Actions = new List<string> { UserAction.Edit, UserAction.Chat },
Actions = new List<string> { UserAction.Edit, UserAction.Train, UserAction.Evaluate, UserAction.Chat },
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
}

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
namespace BotSharp.Core.Agents.Services;
@ -6,11 +7,10 @@ public partial class AgentService
{
public async Task<bool> DeleteAgent(string id)
{
var user = _db.GetUserById(_user.Id);
var userAgents = await GetUserAgents(user?.Id);
var found = userAgents?.FirstOrDefault(x => x.AgentId == id);
var userService = _services.GetRequiredService<IUserService>();
var auth = await userService.GetUserAuthorizations(new List<string> { id });
if (!UserConstant.AdminRoles.Contains(user?.Role) && (found?.Actions == null || !found.Actions.Contains(UserAction.Edit)))
if (!auth.IsAgentActionAllowed(id, UserAction.Edit))
{
return false;
}

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Repositories.Enums;
using BotSharp.Abstraction.Users.Enums;
using System.IO;
namespace BotSharp.Core.Agents.Services;
@ -17,8 +16,9 @@ public partial class AgentService
return refreshResult;
}
var user = _db.GetUserById(_user.Id);
if (!UserConstant.AdminRoles.Contains(user.Role))
var userService = _services.GetRequiredService<IUserService>();
var isValid = await userService.IsAdminUser(_user.Id);
if (!isValid)
{
return "Unauthorized user.";
}

View file

@ -1,6 +1,7 @@
using BotSharp.Abstraction.Repositories.Enums;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
using System.IO;
namespace BotSharp.Core.Agents.Services;
@ -12,12 +13,10 @@ public partial class AgentService
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
var auth = await userService.GetUserAuthorizations(new List<string> { agent.Id });
var allowEdit = auth.IsAgentActionAllowed(agent.Id, UserAction.Edit);
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)))
if (!allowEdit)
{
return;
}

View file

@ -9,6 +9,7 @@ using BotSharp.Abstraction.Users.Settings;
using BotSharp.Abstraction.Interpreters.Settings;
using BotSharp.Abstraction.Infrastructures;
using BotSharp.Core.Processors;
using BotSharp.Core.Roles.Services;
namespace BotSharp.Core;
@ -23,6 +24,7 @@ public static class BotSharpCoreExtensions
services.AddSingleton<DistributedLocker>();
services.AddScoped<ISettingService, SettingService>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<ProcessorFactory>();

View file

@ -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<Agent> GetAgents(AgentFilter filter)
{
if (filter == null)
{
filter = AgentFilter.Empty();
}
var query = Agents;
if (!string.IsNullOrEmpty(filter.AgentName))
{
@ -526,7 +528,7 @@ namespace BotSharp.Core.Repository
var agentDir = GetAgentDataDir(agentId);
if (string.IsNullOrEmpty(agentDir)) return false;
// Delete agent user relationships
// Delete user agents
var usersDir = Path.Combine(_dbSettings.FileRepository, USERS_FOLDER);
if (Directory.Exists(usersDir))
{
@ -544,6 +546,24 @@ namespace BotSharp.Core.Repository
}
}
// Delete role agents
var rolesDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
if (Directory.Exists(rolesDir))
{
foreach (var roleDir in Directory.GetDirectories(rolesDir))
{
var roleAgentFile = Directory.GetFiles(roleDir).FirstOrDefault(x => Path.GetFileName(x) == ROLE_AGENT_FILE);
if (string.IsNullOrEmpty(roleAgentFile)) continue;
var text = File.ReadAllText(roleAgentFile);
var roleAgents = JsonSerializer.Deserialize<List<RoleAgent>>(text, _options);
if (roleAgents.IsNullOrEmpty()) continue;
roleAgents = roleAgents?.Where(x => x.AgentId != agentId)?.ToList() ?? [];
File.WriteAllText(roleAgentFile, JsonSerializer.Serialize(roleAgents, _options));
}
}
// Delete agent folder
Directory.Delete(agentDir, true);
Reset();
@ -559,6 +579,7 @@ namespace BotSharp.Core.Repository
{
_agents = [];
_userAgents = [];
_roleAgents = [];
}
}
}

View file

@ -8,6 +8,11 @@ public partial class FileRepository
#region Task
public PagedItems<AgentTask> GetAgentTasks(AgentTaskFilter filter)
{
if (filter == null)
{
filter = AgentTaskFilter.Empty();
}
var tasks = new List<AgentTask>();
var pager = filter.Pager ?? new Pagination();
var skipCount = 0;

View file

@ -322,6 +322,11 @@ namespace BotSharp.Core.Repository
public PagedItems<Conversation> GetConversations(ConversationFilter filter)
{
if (filter == null)
{
filter = ConversationFilter.Empty();
}
var records = new List<Conversation>();
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
var pager = filter?.Pager ?? new Pagination();

View file

@ -0,0 +1,138 @@
using System.IO;
namespace BotSharp.Core.Repository;
public partial class FileRepository
{
public bool RefreshRoles(IEnumerable<Role> roles)
{
if (roles.IsNullOrEmpty()) return false;
var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id)
&& !string.IsNullOrWhiteSpace(x.Name)).ToList();
if (validRoles.IsNullOrEmpty()) return false;
var baseDir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
if (Directory.Exists(baseDir))
{
Directory.Delete(baseDir, true);
}
Directory.CreateDirectory(baseDir);
foreach (var role in validRoles)
{
var dir = Path.Combine(baseDir, role.Id);
Directory.CreateDirectory(dir);
Thread.Sleep(50);
var roleFile = Path.Combine(dir, ROLE_FILE);
role.CreatedTime = DateTime.UtcNow;
role.UpdatedTime = DateTime.UtcNow;
File.WriteAllText(roleFile, JsonSerializer.Serialize(role, _options));
}
return true;
}
public IEnumerable<Role> 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, bool includeAgent = false)
{
if (string.IsNullOrWhiteSpace(roleId)) return null;
var role = Roles.FirstOrDefault(x => x.Id == roleId);
if (role == null) return null;
var agentActions = new List<RoleAgentAction>();
var roleAgents = RoleAgents?.Where(x => x.RoleId == roleId)?.ToList() ?? [];
if (!includeAgent)
{
agentActions = roleAgents.Select(x => new RoleAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Actions = x.Actions
}).ToList();
role.AgentActions = agentActions;
return role;
}
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 updateRoleAgents = 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 (updateRoleAgents)
{
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;
}
}

View file

@ -1,7 +1,5 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Enums;
using BotSharp.Abstraction.Users.Models;
using System;
using System.IO;
namespace BotSharp.Core.Repository;
@ -73,6 +71,11 @@ public partial class FileRepository
public PagedItems<User> GetUsers(UserFilter filter)
{
if (filter == null)
{
filter = UserFilter.Empty();
}
var users = Users;
// Apply filters
@ -92,39 +95,15 @@ public partial class FileRepository
{
users = users.Where(x => filter.Roles.Contains(x.Role));
}
if (!filter.Types.IsNullOrEmpty())
{
users = users.Where(x => filter.Types.Contains(x.Type));
}
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<User>
{
Items = users.OrderByDescending(x => x.CreatedTime).Skip(filter.Offset).Take(filter.Size),
@ -132,7 +111,53 @@ public partial class FileRepository
};
}
public bool UpdateUser(User user, bool isUpdateUserAgents = false)
public User? GetUserDetails(string userId, bool includeAgent = false)
{
if (string.IsNullOrWhiteSpace(userId)) return null;
var user = Users.FirstOrDefault(x => x.Id == userId || x.ExternalId == userId);
if (user == null) return null;
var agentActions = new List<UserAgentAction>();
var userAgents = UserAgents?.Where(x => x.UserId == userId)?.ToList() ?? [];
if (!includeAgent)
{
agentActions = userAgents.Select(x => new UserAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Actions = x.Actions
}).ToList();
user.AgentActions = agentActions;
return user;
}
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 updateUserAgents = false)
{
if (string.IsNullOrEmpty(user?.Id)) return false;
@ -146,7 +171,7 @@ public partial class FileRepository
user.UpdatedTime = DateTime.UtcNow;
File.WriteAllText(userFile, JsonSerializer.Serialize(user, _options));
if (isUpdateUserAgents)
if (updateUserAgents)
{
var userAgents = user.AgentActions?.Select(x => new UserAgent
{
@ -160,10 +185,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;
}
}

View file

@ -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<Role> _roles = new List<Role>();
private List<User> _users = new List<User>();
private List<Agent> _agents = new List<Agent>();
private List<RoleAgent> _roleAgents = new List<RoleAgent>();
private List<UserAgent> _userAgents = new List<UserAgent>();
private List<Conversation> _conversations = new List<Conversation>();
private PluginConfig? _pluginConfig = null;
private IQueryable<Role> Roles
{
get
{
if (!_roles.IsNullOrEmpty())
{
return _roles.AsQueryable();
}
var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
_roles = new List<Role>();
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<Role>(json, _options));
}
}
return _roles.AsQueryable();
}
}
private IQueryable<RoleAgent> RoleAgents
{
get
{
if (!_roleAgents.IsNullOrEmpty())
{
return _roleAgents.AsQueryable();
}
var dir = Path.Combine(_dbSettings.FileRepository, ROLES_FOLDER);
_roleAgents = new List<RoleAgent>();
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<List<RoleAgent>>(json, _options));
}
}
return _roleAgents.AsQueryable();
}
}
private IQueryable<User> Users
{
get

View file

@ -0,0 +1,65 @@
using BotSharp.Abstraction.Users.Enums;
using System.Reflection;
namespace BotSharp.Core.Roles.Services;
public class RoleService : IRoleService
{
private readonly IServiceProvider _services;
private readonly ILogger<RoleService> _logger;
public RoleService(
IServiceProvider services,
ILogger<RoleService> logger)
{
_services = services;
_logger = logger;
}
public async Task<bool> RefreshRoles()
{
var allRoles = await GetRoleOptions();
var roles = allRoles.Select(x => new Role { Id = Guid.NewGuid().ToString(), Name = x }).ToList();
var db = _services.GetRequiredService<IBotSharpRepository>();
return db.RefreshRoles(roles);
}
public async Task<IEnumerable<string>> 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<IEnumerable<Role>> GetRoles(RoleFilter filter)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var roles = db.GetRoles(filter);
return roles;
}
public async Task<Role?> GetRoleDetails(string roleId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var role = db.GetRoleDetails(roleId, includeAgent: true);
return role;
}
public async Task<bool> 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<IBotSharpRepository>();
return db.UpdateRole(role, isUpdateRoleAgents);
}
}

View file

@ -407,10 +407,63 @@ public class UserService : IUserService
return users;
}
public async Task<bool> UpdateUser(User model, bool isUpdateUserAgents = false)
public async Task<bool> IsAdminUser(string userId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
return db.UpdateUser(model, isUpdateUserAgents);
var user = db.GetUserById(userId);
return user != null && UserConstant.AdminRoles.Contains(user.Role);
}
public async Task<UserAuthorization> GetUserAuthorizations(IEnumerable<string>? agentIds = null)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var user = db.GetUserById(_user.Id);
var auth = new UserAuthorization();
if (user == null) return auth;
auth.IsAdmin = UserConstant.AdminRoles.Contains(user.Role);
var role = db.GetRoles(new RoleFilter { Names = [user.Role] }).FirstOrDefault();
var permissions = user.Permissions?.Any() == true ? user.Permissions : role?.Permissions ?? [];
auth.Permissions = permissions;
if (agentIds == null || !agentIds.Any())
{
return auth;
}
var userAgents = db.GetUserDetails(user.Id)?.AgentActions?
.Where(x => agentIds.Contains(x.AgentId) && x.Actions.Any())?.Select(x => new UserAgent
{
AgentId = x.AgentId,
Actions = x.Actions
}).ToList() ?? [];
var userAgentIds = userAgents.Select(x => x.AgentId).ToList();
var roleAgents = db.GetRoleDetails(role?.Id)?.AgentActions?
.Where(x => !userAgentIds.Contains(x.AgentId))?.Select(x => new UserAgent
{
AgentId = x.AgentId,
Actions = x.Actions
})?.ToList() ?? [];
auth.AgentActions = userAgents.Concat(roleAgents);
return auth;
}
public async Task<User?> GetUserDetails(string userId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
return db.GetUserDetails(userId, includeAgent: true);
}
public async Task<bool> UpdateUser(User user, bool isUpdateUserAgents = false)
{
if (user == null) return false;
var db = _services.GetRequiredService<IBotSharpRepository>();
return db.UpdateUser(user, isUpdateUserAgents);
}
public async Task<Token> ActiveUser(UserActivationModel model)

View file

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

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Users.Enums;
namespace BotSharp.OpenAPI.Controllers;
@ -58,20 +57,13 @@ public class AgentController : ControllerBase
rule.RedirectToAgentName = found.Name;
}
var editable = true;
var chatable = true;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (!UserConstant.AdminRoles.Contains(user?.Role))
{
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);
}
var auth = await userService.GetUserAuthorizations(new List<string> { targetAgent.Id });
targetAgent.Editable = editable;
targetAgent.Chatable = chatable;
targetAgent.Editable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Edit);
targetAgent.Chatable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Chat);
targetAgent.Trainable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Train);
targetAgent.Evaluable = auth.IsAgentActionAllowed(targetAgent.Id, UserAction.Evaluate);
return targetAgent;
}
@ -94,27 +86,14 @@ public class AgentController : ControllerBase
};
}
var userAgents = new List<UserAgent>();
var user = await userService.GetUser(_user.Id);
if (!UserConstant.AdminRoles.Contains(user.Role))
{
userAgents = await _agentService.GetUserAgents(user.Id);
}
var auth = await userService.GetUserAuthorizations(pagedAgents.Items.Select(x => x.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;
model.Editable = auth.IsAgentActionAllowed(x.Id, UserAction.Edit);
model.Chatable = auth.IsAgentActionAllowed(x.Id, UserAction.Chat);
model.Trainable = auth.IsAgentActionAllowed(x.Id, UserAction.Train);
model.Evaluable = auth.IsAgentActionAllowed(x.Id, UserAction.Evaluate);
return model;
})?.ToList() ?? [];

View file

@ -22,9 +22,8 @@ public class PluginController : ControllerBase
[HttpGet("/plugins")]
public async Task<PagedItems<PluginDef>> GetPlugins([FromQuery] PluginFilter filter)
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (!UserConstant.AdminRoles.Contains(user?.Role))
var isValid = await IsValidUser();
if (!isValid)
{
return new PagedItems<PluginDef>();
}
@ -55,7 +54,11 @@ public class PluginController : ControllerBase
{
Roles = new List<string> { UserRole.Root, UserRole.Admin }
},
new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 33)
new PluginMenuDef("Roles", link: "page/roles", icon: "bx bx-group", weight: 33)
{
Roles = new List<string> { UserRole.Root, UserRole.Admin }
},
new PluginMenuDef("Users", link: "page/users", icon: "bx bx-user", weight: 34)
{
Roles = new List<string> { UserRole.Root, UserRole.Admin }
}
@ -91,4 +94,10 @@ public class PluginController : ControllerBase
var loader = _services.GetRequiredService<PluginLoader>();
return loader.UpdatePluginStatus(_services, id, false);
}
private async Task<bool> IsValidUser()
{
var userService = _services.GetRequiredService<IUserService>();
return await userService.IsAdminUser(_user.Id);
}
}

View file

@ -0,0 +1,88 @@
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;
}
[HttpPost("/role/refresh")]
public async Task<bool> RefreshRoles()
{
var isValid = await IsValidUser();
if (!isValid)
{
return false;
}
return await _roleService.RefreshRoles();
}
[HttpGet("/role/options")]
public async Task<IEnumerable<string>> GetRoleOptions()
{
return await _roleService.GetRoleOptions();
}
[HttpPost("/roles")]
public async Task<IEnumerable<RoleViewModel>> GetRoles([FromBody] RoleFilter? filter = null)
{
if (filter == null)
{
filter = RoleFilter.Empty();
}
var isValid = await IsValidUser();
if (!isValid)
{
return Enumerable.Empty<RoleViewModel>();
}
var roles = await _roleService.GetRoles(filter);
return roles.Select(x => RoleViewModel.FromRole(x)).ToList();
}
[HttpGet("/role/{id}/details")]
public async Task<RoleViewModel> GetRoleDetails([FromRoute] string id)
{
var role = await _roleService.GetRoleDetails(id);
return RoleViewModel.FromRole(role);
}
[HttpPut("/role")]
public async Task<bool> UpdateRole([FromBody] RoleUpdateModel model)
{
if (model == null) return false;
var isValid = await IsValidUser();
if (!isValid)
{
return false;
}
var role = RoleUpdateModel.ToRole(model);
return await _roleService.UpdateRole(role, isUpdateRoleAgents: true);
}
private async Task<bool> IsValidUser()
{
var userService = _services.GetRequiredService<IUserService>();
return await userService.IsAdminUser(_user.Id);
}
}

View file

@ -182,8 +182,8 @@ public class UserController : ControllerBase
public async Task<PagedItems<UserViewModel>> GetUsers([FromBody] UserFilter filter)
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
var isValid = await IsValidUser();
if (!isValid)
{
return new PagedItems<UserViewModel>();
}
@ -198,19 +198,26 @@ public class UserController : ControllerBase
};
}
[HttpGet("/user/{id}/details")]
public async Task<UserViewModel> GetUserDetails(string id)
{
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUserDetails(id);
return UserViewModel.FromUser(user);
}
[HttpPut("/user")]
public async Task<bool> UpdateUser([FromBody] UserUpdateModel model)
{
if (model == null) return false;
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user == null || !UserConstant.AdminRoles.Contains(user.Role))
var isValid = await IsValidUser();
if (!isValid)
{
return false;
}
var userService = _services.GetRequiredService<IUserService>();
var updated = await userService.UpdateUser(UserUpdateModel.ToUser(model), isUpdateUserAgents: true);
return updated;
}
@ -245,6 +252,12 @@ public class UserController : ControllerBase
#region Private methods
private async Task<bool> IsValidUser()
{
var userService = _services.GetRequiredService<IUserService>();
return await userService.IsAdminUser(_user.Id);
}
private FileContentResult BuildFileResult(string file)
{
var fileStorage = _services.GetRequiredService<IFileStorageService>();

View file

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

View file

@ -48,6 +48,8 @@ public class AgentViewModel
public bool Editable { get; set; }
public bool Chatable { get; set; }
public bool Trainable { get; set; }
public bool Evaluable { get; set; }
[JsonPropertyName("created_datetime")]
public DateTime CreatedDateTime { get; set; }

View file

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

View file

@ -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<string> Permissions { get; set; } = [];
[JsonPropertyName("agent_actions")]
public IEnumerable<RoleAgentActionViewModel> 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)) ?? []
};
}
}

View file

@ -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<string> Permissions { get; set; } = [];
[JsonPropertyName("agent_actions")]
public IEnumerable<RoleAgentActionViewModel> 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 != default ? role.CreatedTime : null,
UpdateDate = role.UpdatedTime != default ? role.UpdatedTime : null
};
}
}

View file

@ -0,0 +1,25 @@
using BotSharp.Abstraction.Roles.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class RoleAgentDocument : MongoBase
{
public string RoleId { get; set; }
public string AgentId { get; set; }
public IEnumerable<string> Actions { get; set; } = [];
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public RoleAgent ToRoleAgent()
{
return new RoleAgent
{
Id = Id,
RoleId = RoleId,
AgentId = AgentId,
Actions = Actions,
CreatedTime = CreatedTime,
UpdatedTime = UpdatedTime
};
}
}

View file

@ -0,0 +1,24 @@
using BotSharp.Abstraction.Roles.Models;
namespace BotSharp.Plugin.MongoStorage.Collections;
public class RoleDocument : MongoBase
{
public string Name { get; set; }
public IEnumerable<string> Permissions { get; set; } = [];
public DateTime CreatedTime { get; set; }
public DateTime UpdatedTime { get; set; }
public Role ToRole()
{
return new Role
{
Id = Id,
Name = Name,
Permissions = Permissions,
CreatedTime = CreatedTime,
UpdatedTime = UpdatedTime
};
}
}

View file

@ -159,4 +159,10 @@ public class MongoDbContext
public IMongoCollection<KnowledgeCollectionFileMetaDocument> KnowledgeCollectionFileMeta
=> Database.GetCollection<KnowledgeCollectionFileMetaDocument>($"{_collectionPrefix}_KnowledgeCollectionFileMeta");
public IMongoCollection<RoleDocument> Roles
=> Database.GetCollection<RoleDocument>($"{_collectionPrefix}_Roles");
public IMongoCollection<RoleAgentDocument> RoleAgents
=> Database.GetCollection<RoleAgentDocument>($"{_collectionPrefix}_RoleAgents");
}

View file

@ -2,6 +2,7 @@ using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Models;
using MongoDB.Driver;
namespace BotSharp.Plugin.MongoStorage.Repository;
@ -283,6 +284,11 @@ public partial class MongoRepository
public List<Agent> GetAgents(AgentFilter filter)
{
if (filter == null)
{
filter = AgentFilter.Empty();
}
var agents = new List<Agent>();
var builder = Builders<AgentDocument>.Filter;
var filters = new List<FilterDefinition<AgentDocument>>() { builder.Empty };
@ -327,8 +333,6 @@ public partial class MongoRepository
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
{
Id = x.Id,
@ -339,6 +343,8 @@ public partial class MongoRepository
UpdatedTime = x.UpdatedTime
}).ToList();
var agentIds = found.Select(x => x.AgentId).Distinct().ToList();
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
foreach (var item in res)
{
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
@ -450,6 +456,7 @@ public partial class MongoRepository
try
{
_dc.UserAgents.DeleteMany(Builders<UserAgentDocument>.Filter.Empty);
_dc.RoleAgents.DeleteMany(Builders<RoleAgentDocument>.Filter.Empty);
_dc.Agents.DeleteMany(Builders<AgentDocument>.Filter.Empty);
return true;
}
@ -467,10 +474,12 @@ public partial class MongoRepository
var agentFilter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
var userAgentFilter = Builders<UserAgentDocument>.Filter.Eq(x => x.AgentId, agentId);
var roleAgentFilter = Builders<RoleAgentDocument>.Filter.Eq(x => x.AgentId, agentId);
var agentTaskFilter = Builders<AgentTaskDocument>.Filter.Eq(x => x.AgentId, agentId);
_dc.Agents.DeleteOne(agentFilter);
_dc.UserAgents.DeleteMany(userAgentFilter);
_dc.RoleAgents.DeleteMany(roleAgentFilter);
_dc.AgentTasks.DeleteMany(agentTaskFilter);
return true;
}

View file

@ -8,6 +8,11 @@ public partial class MongoRepository
#region Task
public PagedItems<AgentTask> GetAgentTasks(AgentTaskFilter filter)
{
if (filter == null)
{
filter = AgentTaskFilter.Empty();
}
var pager = filter.Pager ?? new Pagination();
var builder = Builders<AgentTaskDocument>.Filter;
var filters = new List<FilterDefinition<AgentTaskDocument>>() { builder.Empty };

View file

@ -281,6 +281,11 @@ public partial class MongoRepository
public PagedItems<Conversation> GetConversations(ConversationFilter filter)
{
if (filter == null)
{
filter = ConversationFilter.Empty();
}
var convBuilder = Builders<ConversationDocument>.Filter;
var convFilters = new List<FilterDefinition<ConversationDocument>>() { convBuilder.Empty };

View file

@ -0,0 +1,152 @@
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Roles.Models;
namespace BotSharp.Plugin.MongoStorage.Repository;
public partial class MongoRepository
{
public bool RefreshRoles(IEnumerable<Role> roles)
{
if (roles.IsNullOrEmpty()) return false;
var validRoles = roles.Where(x => !string.IsNullOrWhiteSpace(x.Id)
&& !string.IsNullOrWhiteSpace(x.Name)).ToList();
if (validRoles.IsNullOrEmpty()) return false;
// Clear data
_dc.RoleAgents.DeleteMany(Builders<RoleAgentDocument>.Filter.Empty);
_dc.Roles.DeleteMany(Builders<RoleDocument>.Filter.Empty);
var roleDocs = validRoles.Select(x => new RoleDocument
{
Id = x.Id,
Name = x.Name,
Permissions = x.Permissions,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
});
_dc.Roles.InsertMany(roleDocs);
return true;
}
public IEnumerable<Role> GetRoles(RoleFilter filter)
{
if (filter == null)
{
filter = RoleFilter.Empty();
}
var roleBuilder = Builders<RoleDocument>.Filter;
var roleFilters = new List<FilterDefinition<RoleDocument>>() { roleBuilder.Empty };
// Apply filters
if (!filter.Names.IsNullOrEmpty())
{
roleFilters.Add(roleBuilder.In(x => x.Name, filter.Names));
}
// Search
var roleDocs = _dc.Roles.Find(roleBuilder.And(roleFilters)).ToList();
var roles = roleDocs.Select(x => x.ToRole()).ToList();
return roles;
}
public Role? GetRoleDetails(string roleId, bool includeAgent = false)
{
if (string.IsNullOrWhiteSpace(roleId)) return null;
var roleDoc = _dc.Roles.Find(Builders<RoleDocument>.Filter.Eq(x => x.Id, roleId)).FirstOrDefault();
if (roleDoc == null) return null;
var agentActions = new List<RoleAgentAction>();
var role = roleDoc.ToRole();
var roleAgentDocs = _dc.RoleAgents.Find(Builders<RoleAgentDocument>.Filter.Eq(x => x.RoleId, roleId)).ToList();
if (!includeAgent)
{
agentActions = roleAgentDocs.Select(x => new RoleAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Actions = x.Actions
}).ToList();
role.AgentActions = agentActions;
return role;
}
var agentIds = roleAgentDocs.Select(x => x.AgentId).Distinct().ToList();
if (!agentIds.IsNullOrEmpty())
{
var agents = GetAgents(new AgentFilter { AgentIds = agentIds });
foreach (var item in roleAgentDocs)
{
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 updateRoleAgents = false)
{
if (string.IsNullOrEmpty(role?.Id)) return false;
var roleFilter = Builders<RoleDocument>.Filter.Eq(x => x.Id, role.Id);
var roleUpdate = Builders<RoleDocument>.Update
.Set(x => x.Name, role.Name)
.Set(x => x.Permissions, role.Permissions)
.Set(x => x.CreatedTime, DateTime.UtcNow)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Roles.UpdateOne(roleFilter, roleUpdate, _options);
if (updateRoleAgents)
{
var roleAgentDocs = role.AgentActions?.Select(x => new RoleAgentDocument
{
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 toDelete = _dc.RoleAgents.Find(Builders<RoleAgentDocument>.Filter.And(
Builders<RoleAgentDocument>.Filter.Eq(x => x.RoleId, role.Id),
Builders<RoleAgentDocument>.Filter.Nin(x => x.Id, roleAgentDocs.Select(x => x.Id))
)).ToList();
_dc.RoleAgents.DeleteMany(Builders<RoleAgentDocument>.Filter.In(x => x.Id, toDelete.Select(x => x.Id)));
foreach (var doc in roleAgentDocs)
{
var roleAgentFilter = Builders<RoleAgentDocument>.Filter.Eq(x => x.Id, doc.Id);
var roleAgentUpdate = Builders<RoleAgentDocument>.Update
.Set(x => x.Id, doc.Id)
.Set(x => x.RoleId, role.Id)
.Set(x => x.AgentId, doc.AgentId)
.Set(x => x.Actions, doc.Actions)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.RoleAgents.UpdateOne(roleAgentFilter, roleAgentUpdate, _options);
}
}
return true;
}
}

View file

@ -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;
@ -41,22 +43,19 @@ public partial class MongoRepository
public User? GetUserById(string id)
{
var user = _dc.Users.AsQueryable()
.FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
var user = _dc.Users.AsQueryable().FirstOrDefault(x => x.Id == id || (x.ExternalId != null && x.ExternalId == id));
return user != null ? user.ToUser() : null;
}
public List<User> GetUserByIds(List<string> ids)
{
var users = _dc.Users.AsQueryable()
.Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId))).ToList();
var users = _dc.Users.AsQueryable().Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId))).ToList();
return users?.Any() == true ? users.Select(x => x.ToUser()).ToList() : new List<User>();
}
public List<User> GetUsersByAffiliateId(string affiliateId)
{
var users = _dc.Users.AsQueryable()
.Where(x => x.AffiliateId == affiliateId).ToList();
var users = _dc.Users.AsQueryable().Where(x => x.AffiliateId == affiliateId).ToList();
return users?.Any() == true ? users.Select(x => x.ToUser()).ToList() : new List<User>();
}
@ -173,6 +172,11 @@ public partial class MongoRepository
public PagedItems<User> GetUsers(UserFilter filter)
{
if (filter == null)
{
filter = UserFilter.Empty();
}
var userBuilder = Builders<UserDocument>.Filter;
var userFilters = new List<FilterDefinition<UserDocument>>() { userBuilder.Empty };
@ -193,6 +197,10 @@ public partial class MongoRepository
{
userFilters.Add(userBuilder.In(x => x.Role, filter.Roles));
}
if (!filter.Types.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.Type, filter.Types));
}
if (!filter.Sources.IsNullOrEmpty())
{
userFilters.Add(userBuilder.In(x => x.Source, filter.Sources));
@ -207,44 +215,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<string>(),
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<User>
{
Items = users,
@ -252,8 +222,60 @@ public partial class MongoRepository
};
}
public User? GetUserDetails(string userId, bool includeAgent = false)
{
if (string.IsNullOrWhiteSpace(userId)) return null;
public bool UpdateUser(User user, bool isUpdateUserAgents = false)
var userDoc = _dc.Users.AsQueryable().FirstOrDefault(x => x.Id == userId || x.ExternalId == userId);
if (userDoc == null) return null;
var agentActions = new List<UserAgentAction>();
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<string>()
}).ToList();
if (!includeAgent)
{
agentActions = userAgents.Select(x => new UserAgentAction
{
Id = x.Id,
AgentId = x.AgentId,
Actions = x.Actions
}).ToList();
user.AgentActions = agentActions;
return user;
}
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 updateUserAgents = false)
{
if (string.IsNullOrEmpty(user?.Id)) return false;
@ -266,7 +288,7 @@ public partial class MongoRepository
_dc.Users.UpdateOne(userFilter, userUpdate);
if (isUpdateUserAgents)
if (updateUserAgents)
{
var userAgentDocs = user.AgentActions?.Select(x => new UserAgentDocument
{