版本260406
This commit is contained in:
@@ -0,0 +1,540 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OrpaonVision.Core.Results;
|
||||
using OrpaonVision.Model.Security;
|
||||
using OrpaonVision.ConfigApp.Infrastructure.Services;
|
||||
|
||||
namespace OrpaonVision.ConfigApp.Infrastructure.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 权限管理服务实现。
|
||||
/// </summary>
|
||||
public sealed class PermissionService : IPermissionService
|
||||
{
|
||||
private readonly ILogger<PermissionService> _logger;
|
||||
private readonly List<PermissionModel> _permissions;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数。
|
||||
/// </summary>
|
||||
public PermissionService(ILogger<PermissionService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_permissions = new List<PermissionModel>();
|
||||
|
||||
InitializeSampleData();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<PermissionModel> CreatePermission(PermissionModel permission)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (permission == null)
|
||||
{
|
||||
return Result<PermissionModel>.Fail("PERMISSION_NULL", "权限不能为空。");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(permission.Code))
|
||||
{
|
||||
return Result<PermissionModel>.Fail("PERMISSION_CODE_REQUIRED", "权限编码不能为空。");
|
||||
}
|
||||
|
||||
// 检查权限编码是否已存在
|
||||
if (_permissions.Any(p => p.Code.Equals(permission.Code, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return Result<PermissionModel>.Fail("PERMISSION_CODE_EXISTS", "权限编码已存在。");
|
||||
}
|
||||
|
||||
_logger.LogInformation("正在创建权限: {PermissionCode}", permission.Code);
|
||||
|
||||
permission.Id = Guid.NewGuid();
|
||||
permission.Status = PermissionStatus.Enabled;
|
||||
permission.SortOrder = _permissions.Count + 1;
|
||||
permission.CreatedAtUtc = DateTime.UtcNow;
|
||||
permission.UpdatedAtUtc = DateTime.UtcNow;
|
||||
|
||||
_permissions.Add(permission);
|
||||
|
||||
_logger.LogInformation("权限创建成功: {PermissionId} - {PermissionCode}", permission.Id, permission.Code);
|
||||
return Result<PermissionModel>.Success(permission, message: "权限创建成功。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "创建权限失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "CREATE_PERMISSION_FAILED", "创建权限失败。", traceId);
|
||||
return Result<PermissionModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<PermissionModel> UpdatePermission(PermissionModel permission)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (permission == null)
|
||||
{
|
||||
return Result<PermissionModel>.Fail("PERMISSION_NULL", "权限不能为空。");
|
||||
}
|
||||
|
||||
var existingPermission = _permissions.FirstOrDefault(p => p.Id == permission.Id);
|
||||
if (existingPermission == null)
|
||||
{
|
||||
return Result<PermissionModel>.Fail("PERMISSION_NOT_FOUND", $"未找到ID为 {permission.Id} 的权限。");
|
||||
}
|
||||
|
||||
_logger.LogInformation("正在更新权限: {PermissionId} - {PermissionCode}", permission.Id, permission.Code);
|
||||
|
||||
existingPermission.Name = permission.Name;
|
||||
existingPermission.Description = permission.Description;
|
||||
existingPermission.Module = permission.Module;
|
||||
existingPermission.Action = permission.Action;
|
||||
existingPermission.SortOrder = permission.SortOrder;
|
||||
existingPermission.Remark = permission.Remark;
|
||||
existingPermission.UpdatedAtUtc = DateTime.UtcNow;
|
||||
existingPermission.UpdatedBy = permission.UpdatedBy;
|
||||
|
||||
_logger.LogInformation("权限更新成功: {PermissionId} - {PermissionCode}", permission.Id, permission.Code);
|
||||
return Result<PermissionModel>.Success(existingPermission, message: "权限更新成功。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "更新权限失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "UPDATE_PERMISSION_FAILED", "更新权限失败。", traceId);
|
||||
return Result<PermissionModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result DeletePermission(Guid permissionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(p => p.Id == permissionId);
|
||||
if (permission == null)
|
||||
{
|
||||
return Result.Fail("PERMISSION_NOT_FOUND", $"未找到ID为 {permissionId} 的权限。");
|
||||
}
|
||||
|
||||
if (permission.IsSystemPermission)
|
||||
{
|
||||
return Result.Fail("CANNOT_DELETE_SYSTEM_PERMISSION", "不能删除系统权限。");
|
||||
}
|
||||
|
||||
_logger.LogInformation("正在删除权限: {PermissionId} - {PermissionCode}", permission.Id, permission.Code);
|
||||
|
||||
_permissions.Remove(permission);
|
||||
|
||||
_logger.LogInformation("权限删除成功: {PermissionId} - {PermissionCode}", permission.Id, permission.Code);
|
||||
return Result.Success(message: "权限删除成功。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "删除权限失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "DELETE_PERMISSION_FAILED", "删除权限失败。", traceId);
|
||||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<PermissionModel> GetPermissionById(Guid permissionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var permission = _permissions.FirstOrDefault(p => p.Id == permissionId);
|
||||
if (permission == null)
|
||||
{
|
||||
return Result<PermissionModel>.Fail("PERMISSION_NOT_FOUND", $"未找到ID为 {permissionId} 的权限。");
|
||||
}
|
||||
|
||||
return Result<PermissionModel>.Success(permission, message: "获取权限成功。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "获取权限失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "GET_PERMISSION_FAILED", "获取权限失败。", traceId);
|
||||
return Result<PermissionModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<PagedResult<PermissionModel>> GetPermissionPagedList(int pageIndex = 1, int pageSize = 20, PermissionStatus? status = null, string? keyword = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _permissions.AsQueryable();
|
||||
|
||||
// 状态过滤
|
||||
if (status.HasValue)
|
||||
{
|
||||
query = query.Where(p => p.Status == status.Value);
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if (!string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
query = query.Where(p =>
|
||||
p.Code.Contains(keyword, StringComparison.OrdinalIgnoreCase) ||
|
||||
p.Name.Contains(keyword, StringComparison.OrdinalIgnoreCase) ||
|
||||
p.Module.Contains(keyword, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
// 排序
|
||||
query = query.OrderBy(p => p.Module).ThenBy(p => p.SortOrder).ThenBy(p => p.Code);
|
||||
|
||||
var totalCount = query.Count();
|
||||
var items = query.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList();
|
||||
|
||||
var pagedResult = PagedResult<PermissionModel>.Success(items, totalCount, pageIndex, pageSize);
|
||||
|
||||
return Result<PagedResult<PermissionModel>>.Success(pagedResult, message: "获取权限列表成功。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "获取权限列表失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "GET_PERMISSION_LIST_FAILED", "获取权限列表失败。", traceId);
|
||||
return Result<PagedResult<PermissionModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<List<PermissionModel>> GetEnabledPermissions()
|
||||
{
|
||||
try
|
||||
{
|
||||
var enabledPermissions = _permissions
|
||||
.Where(p => p.Status == PermissionStatus.Enabled)
|
||||
.OrderBy(p => p.Module)
|
||||
.ThenBy(p => p.SortOrder)
|
||||
.ThenBy(p => p.Code)
|
||||
.ToList();
|
||||
|
||||
return Result<List<PermissionModel>>.Success(enabledPermissions, message: "获取启用权限列表成功。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "获取启用权限列表失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "GET_ENABLED_PERMISSIONS_FAILED", "获取启用权限列表失败。", traceId);
|
||||
return Result<List<PermissionModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<List<PermissionModel>> GetPermissionsByModule(string module)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(module))
|
||||
{
|
||||
return Result<List<PermissionModel>>.Fail("MODULE_REQUIRED", "模块名称不能为空。");
|
||||
}
|
||||
|
||||
var modulePermissions = _permissions
|
||||
.Where(p => p.Module.Equals(module, StringComparison.OrdinalIgnoreCase) && p.Status == PermissionStatus.Enabled)
|
||||
.OrderBy(p => p.SortOrder)
|
||||
.ThenBy(p => p.Code)
|
||||
.ToList();
|
||||
|
||||
return Result<List<PermissionModel>>.Success(modulePermissions, message: "获取模块权限列表成功。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "获取模块权限列表失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "GET_MODULE_PERMISSIONS_FAILED", "获取模块权限列表失败。", traceId);
|
||||
return Result<List<PermissionModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<bool> CheckPermissionCodeExists(string code, Guid? excludePermissionId = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
return Result<bool>.Fail("PERMISSION_CODE_REQUIRED", "权限编码不能为空。");
|
||||
}
|
||||
|
||||
var query = _permissions.Where(p => p.Code.Equals(code, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (excludePermissionId.HasValue)
|
||||
{
|
||||
query = query.Where(p => p.Id != excludePermissionId.Value);
|
||||
}
|
||||
|
||||
var exists = query.Any();
|
||||
return Result<bool>.Success(exists, message: exists ? "权限编码已存在" : "权限编码可用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "检查权限编码是否存在失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "CHECK_PERMISSION_CODE_EXISTS_FAILED", "检查权限编码是否存在失败。", traceId);
|
||||
return Result<bool>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化示例数据。
|
||||
/// </summary>
|
||||
private void InitializeSampleData()
|
||||
{
|
||||
var samplePermissions = new List<PermissionModel>
|
||||
{
|
||||
// 用户管理权限
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "user.view",
|
||||
Name = "查看用户",
|
||||
Description = "查看用户列表和详情",
|
||||
Type = PermissionType.Function,
|
||||
Module = "user",
|
||||
Action = "view",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 1,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "user.create",
|
||||
Name = "创建用户",
|
||||
Description = "创建新用户",
|
||||
Type = PermissionType.Function,
|
||||
Module = "user",
|
||||
Action = "create",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 2,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "user.edit",
|
||||
Name = "编辑用户",
|
||||
Description = "编辑用户信息",
|
||||
Type = PermissionType.Function,
|
||||
Module = "user",
|
||||
Action = "edit",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 3,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "user.delete",
|
||||
Name = "删除用户",
|
||||
Description = "删除用户",
|
||||
Type = PermissionType.Function,
|
||||
Module = "user",
|
||||
Action = "delete",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 4,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
|
||||
// 角色管理权限
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "role.view",
|
||||
Name = "查看角色",
|
||||
Description = "查看角色列表和详情",
|
||||
Type = PermissionType.Function,
|
||||
Module = "role",
|
||||
Action = "view",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 1,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "role.create",
|
||||
Name = "创建角色",
|
||||
Description = "创建新角色",
|
||||
Type = PermissionType.Function,
|
||||
Module = "role",
|
||||
Action = "create",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 2,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "role.edit",
|
||||
Name = "编辑角色",
|
||||
Description = "编辑角色信息",
|
||||
Type = PermissionType.Function,
|
||||
Module = "role",
|
||||
Action = "edit",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 3,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "role.delete",
|
||||
Name = "删除角色",
|
||||
Description = "删除角色",
|
||||
Type = PermissionType.Function,
|
||||
Module = "role",
|
||||
Action = "delete",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 4,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
|
||||
// 训练任务管理权限
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "training.view",
|
||||
Name = "查看训练任务",
|
||||
Description = "查看训练任务列表和详情",
|
||||
Type = PermissionType.Function,
|
||||
Module = "training",
|
||||
Action = "view",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 1,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "training.create",
|
||||
Name = "创建训练任务",
|
||||
Description = "创建新的训练任务",
|
||||
Type = PermissionType.Function,
|
||||
Module = "training",
|
||||
Action = "create",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 2,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "training.start",
|
||||
Name = "启动训练任务",
|
||||
Description = "启动训练任务",
|
||||
Type = PermissionType.Function,
|
||||
Module = "training",
|
||||
Action = "start",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 3,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "training.stop",
|
||||
Name = "停止训练任务",
|
||||
Description = "停止训练任务",
|
||||
Type = PermissionType.Function,
|
||||
Module = "training",
|
||||
Action = "stop",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 4,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
|
||||
// 系统管理权限
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "system.config",
|
||||
Name = "系统配置",
|
||||
Description = "系统配置管理",
|
||||
Type = PermissionType.Function,
|
||||
Module = "system",
|
||||
Action = "config",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 1,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
},
|
||||
new PermissionModel
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Code = "system.log",
|
||||
Name = "查看日志",
|
||||
Description = "查看系统日志",
|
||||
Type = PermissionType.Function,
|
||||
Module = "system",
|
||||
Action = "log",
|
||||
Status = PermissionStatus.Enabled,
|
||||
IsSystemPermission = true,
|
||||
SortOrder = 2,
|
||||
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
UpdatedAtUtc = DateTime.UtcNow.AddDays(-30),
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System"
|
||||
}
|
||||
};
|
||||
|
||||
_permissions.AddRange(samplePermissions);
|
||||
_logger.LogInformation("已初始化 {Count} 个示例权限", samplePermissions.Count);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user