mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-24 05:26:58 +00:00
feat: 新增权限指令并集成到多个页面组件
refactor(backend): 重构数据库模型和初始化逻辑 fix(frontend): 修复权限指令在多个组件中的使用问题 perf(backend): 优化数据库连接和初始化性能 docs: 更新低代码生成器的README文档 style: 清理无用代码和注释 chore: 移除不再需要的依赖项 test: 更新用户权限相关测试用例 ci: 更新CI配置以支持新的权限检查 build: 更新依赖项版本
This commit is contained in:
@@ -1,191 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const GENERATOR_BASE_URL = "/api/v1/codegen";
|
||||
|
||||
const GeneratorAPI = {
|
||||
/** 获取数据表分页列表 */
|
||||
getTablePage(params: TablePageQuery) {
|
||||
return request<any, PageResult<TablePageVO[]>>({
|
||||
url: `${GENERATOR_BASE_URL}/table/page`,
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
},
|
||||
|
||||
/** 获取代码生成配置 */
|
||||
getGenConfig(tableName: string) {
|
||||
return request<any, GenConfigForm>({
|
||||
url: `${GENERATOR_BASE_URL}/${tableName}/config`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
|
||||
/** 获取代码生成配置 */
|
||||
saveGenConfig(tableName: string, data: GenConfigForm) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/${tableName}/config`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
},
|
||||
|
||||
/** 获取代码生成预览数据 */
|
||||
getPreviewData(tableName: string) {
|
||||
return request<any, GeneratorPreviewVO[]>({
|
||||
url: `${GENERATOR_BASE_URL}/${tableName}/preview`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
|
||||
/** 重置代码生成配置 */
|
||||
resetGenConfig(tableName: string) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/${tableName}/config`,
|
||||
method: "delete",
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 下载 ZIP 文件
|
||||
* @param url
|
||||
* @param fileName
|
||||
*/
|
||||
download(tableName: string) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/${tableName}/download`,
|
||||
method: "get",
|
||||
responseType: "blob",
|
||||
}).then((response) => {
|
||||
const fileName = decodeURI(
|
||||
response.headers["content-disposition"].split(";")[1].split("=")[1]
|
||||
);
|
||||
|
||||
const blob = new Blob([response.data], { type: "application/zip" });
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default GeneratorAPI;
|
||||
|
||||
/** 代码生成预览对象 */
|
||||
export interface GeneratorPreviewVO {
|
||||
/** 文件生成路径 */
|
||||
path: string;
|
||||
/** 文件名称 */
|
||||
fileName: string;
|
||||
/** 文件内容 */
|
||||
content: string;
|
||||
}
|
||||
|
||||
/** 数据表分页查询参数 */
|
||||
export interface TablePageQuery extends PageQuery {
|
||||
/** 关键字(表名) */
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/** 数据表分页对象 */
|
||||
export interface TablePageVO {
|
||||
/** 表名称 */
|
||||
tableName: string;
|
||||
|
||||
/** 表描述 */
|
||||
tableComment: string;
|
||||
|
||||
/** 存储引擎 */
|
||||
engine: string;
|
||||
|
||||
/** 字符集排序规则 */
|
||||
tableCollation: string;
|
||||
|
||||
/** 创建时间 */
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
/** 代码生成配置表单 */
|
||||
export interface GenConfigForm {
|
||||
/** 主键 */
|
||||
id?: string;
|
||||
|
||||
/** 表名 */
|
||||
tableName?: string;
|
||||
|
||||
/** 业务名 */
|
||||
businessName?: string;
|
||||
|
||||
/** 模块名 */
|
||||
moduleName?: string;
|
||||
|
||||
/** 包名 */
|
||||
packageName?: string;
|
||||
|
||||
/** 实体名 */
|
||||
entityName?: string;
|
||||
|
||||
/** 作者 */
|
||||
author?: string;
|
||||
|
||||
/** 上级菜单 */
|
||||
parentMenuId?: string;
|
||||
|
||||
/** 后端应用名 */
|
||||
backendAppName?: string;
|
||||
/** 前端应用名 */
|
||||
frontendAppName?: string;
|
||||
|
||||
/** 字段配置列表 */
|
||||
fieldConfigs?: FieldConfig[];
|
||||
}
|
||||
|
||||
/** 字段配置 */
|
||||
export interface FieldConfig {
|
||||
/** 主键 */
|
||||
id?: string;
|
||||
|
||||
/** 列名 */
|
||||
columnName?: string;
|
||||
|
||||
/** 列类型 */
|
||||
columnType?: string;
|
||||
|
||||
/** 字段名 */
|
||||
fieldName?: string;
|
||||
|
||||
/** 字段类型 */
|
||||
fieldType?: string;
|
||||
|
||||
/** 字段描述 */
|
||||
fieldComment?: string;
|
||||
|
||||
/** 是否在列表显示 */
|
||||
isShowInList?: number;
|
||||
|
||||
/** 是否在表单显示 */
|
||||
isShowInForm?: number;
|
||||
|
||||
/** 是否在查询条件显示 */
|
||||
isShowInQuery?: number;
|
||||
|
||||
/** 是否必填 */
|
||||
isRequired?: number;
|
||||
|
||||
/** 表单类型 */
|
||||
formType?: number;
|
||||
|
||||
/** 查询类型 */
|
||||
queryType?: number;
|
||||
|
||||
/** 字段长度 */
|
||||
maxLength?: number;
|
||||
|
||||
/** 字段排序 */
|
||||
fieldSort?: number;
|
||||
|
||||
/** 字典类型 */
|
||||
dictType?: string;
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const GENERATOR_BASE_URL = "/gencode";
|
||||
|
||||
const GencodeAPI = {
|
||||
|
||||
// 查询生成表数据
|
||||
listTable(query: TablePageQuery) {
|
||||
return request<PageResult<GenTableOutVO>>({
|
||||
url: `${GENERATOR_BASE_URL}/list`,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
},
|
||||
|
||||
// 查询db数据库列表
|
||||
listDbTable(query: TablePageQuery) {
|
||||
return request<PageResult<TablePageVO>>({
|
||||
url: `${GENERATOR_BASE_URL}/db/list`,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
},
|
||||
|
||||
// 查询表详细信息
|
||||
getGenTableDetail(tableId: number) {
|
||||
return request<GenTableDetailResult>({
|
||||
url: `${GENERATOR_BASE_URL}/detail/${tableId}`,
|
||||
method: 'get'
|
||||
})
|
||||
},
|
||||
|
||||
// 创建表
|
||||
createTable(sql: string) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/create`,
|
||||
method: 'post',
|
||||
params: { sql }
|
||||
})
|
||||
},
|
||||
|
||||
// 修改代码生成信息
|
||||
updateGenTable(data: GenTableUpdateSchema) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/update`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
},
|
||||
|
||||
// 导入表
|
||||
importTable(tables: string[]) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/import`,
|
||||
method: 'post',
|
||||
data: { tables }
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
// 预览生成代码
|
||||
previewTable(tableId: number) {
|
||||
return request<GeneratorPreviewVO[]>({
|
||||
url: `${GENERATOR_BASE_URL}/preview/${tableId}`,
|
||||
method: 'get'
|
||||
})
|
||||
},
|
||||
|
||||
// 删除表数据
|
||||
deleteTable(tableIds: number[]) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/delete`,
|
||||
method: 'delete',
|
||||
data: tableIds
|
||||
})
|
||||
},
|
||||
|
||||
// 批量生成代码
|
||||
batchGenCode(tables: string) {
|
||||
return request<Blob>({
|
||||
url: `${GENERATOR_BASE_URL}/batch/out`,
|
||||
method: 'patch',
|
||||
params: { tables },
|
||||
responseType: 'blob'
|
||||
})
|
||||
},
|
||||
|
||||
// 生成代码到指定路径
|
||||
genCodeToPath(tableName: string) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/out/path/${tableName}`,
|
||||
method: 'post'
|
||||
})
|
||||
},
|
||||
|
||||
// 同步数据库
|
||||
syncDb(tableName: string) {
|
||||
return request({
|
||||
url: `${GENERATOR_BASE_URL}/sync/db/${tableName}`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
export default GencodeAPI;
|
||||
|
||||
/** 代码生成预览对象 */
|
||||
export interface GeneratorPreviewVO {
|
||||
/** 文件生成路径 */
|
||||
path: string;
|
||||
/** 文件名称 */
|
||||
fileName: string;
|
||||
/** 文件内容 */
|
||||
content: string;
|
||||
}
|
||||
|
||||
/** 数据表分页查询参数 */
|
||||
export interface TablePageQuery extends PageQuery {
|
||||
/** 关键字(表名) */
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/** 数据表分页对象 */
|
||||
export interface TablePageVO {
|
||||
/** 表名称 */
|
||||
tableName: string;
|
||||
|
||||
/** 表描述 */
|
||||
tableComment: string;
|
||||
|
||||
/** 存储引擎 */
|
||||
engine: string;
|
||||
|
||||
/** 字符集排序规则 */
|
||||
tableCollation: string;
|
||||
|
||||
/** 创建时间 */
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
/** 代码生成表输出对象 */
|
||||
export interface GenTableOutVO {
|
||||
/** 主键 */
|
||||
id?: number;
|
||||
/** 表名称 */
|
||||
tableName: string;
|
||||
/** 表描述 */
|
||||
tableComment: string;
|
||||
/** 关联子表的表名 */
|
||||
subTableName?: string;
|
||||
/** 子表关联的外键名 */
|
||||
subTableFkName: string;
|
||||
/** 实体类名称 */
|
||||
className: string;
|
||||
/** 使用的模板(crud单表操作 tree树表操作) */
|
||||
tplCategory?: string;
|
||||
/** 前端模板类型(element-ui模版 element-plus模版) */
|
||||
tplWebType?: string;
|
||||
/** 生成包路径 */
|
||||
packageName: string;
|
||||
/** 生成模块名 */
|
||||
moduleName: string;
|
||||
/** 生成业务名 */
|
||||
businessName: string;
|
||||
/** 生成功能名 */
|
||||
functionName: string;
|
||||
/** 生成功能作者 */
|
||||
functionAuthor?: string;
|
||||
/** 生成代码方式(0zip压缩包 1自定义路径) */
|
||||
genType?: string;
|
||||
/** 生成路径(不填默认项目路径) */
|
||||
genPath?: string;
|
||||
/** 其它生成选项 */
|
||||
options?: string;
|
||||
/** 树编码字段 */
|
||||
treeCode?: string;
|
||||
/** 树父编码字段 */
|
||||
treeParentCode?: string;
|
||||
/** 树名称字段 */
|
||||
treeName?: string;
|
||||
/** 上级菜单ID字段 */
|
||||
parentMenuId?: number;
|
||||
/** 上级菜单名称字段 */
|
||||
parentMenuName?: string;
|
||||
/** 是否为子表 */
|
||||
sub?: boolean;
|
||||
/** 是否为树表 */
|
||||
tree?: boolean;
|
||||
/** 是否为单表 */
|
||||
crud?: boolean;
|
||||
}
|
||||
|
||||
/** 代码生成表更新模型 */
|
||||
export interface GenTableUpdateSchema extends GenTableOutVO {
|
||||
/** 主键信息 */
|
||||
pkColumn?: GenTableColumnUpdateSchema;
|
||||
/** 子表信息 */
|
||||
subTable?: GenTableUpdateSchema;
|
||||
/** 表列信息 */
|
||||
columns: GenTableColumnUpdateSchema[];
|
||||
}
|
||||
|
||||
/** 代码生成表列更新模型 */
|
||||
export interface GenTableColumnUpdateSchema {
|
||||
/** 主键 */
|
||||
id?: number;
|
||||
/** 归属表编号 */
|
||||
tableId?: number;
|
||||
/** 列名称 */
|
||||
columnName: string;
|
||||
/** 列描述 */
|
||||
columnComment?: string;
|
||||
/** 列类型 */
|
||||
columnType: string;
|
||||
/** PYTHON类型 */
|
||||
pythonType?: string;
|
||||
/** PYTHON字段名 */
|
||||
pythonField: string;
|
||||
/** 是否主键(1是) */
|
||||
isPk?: string;
|
||||
/** 是否自增(1是) */
|
||||
isIncrement?: string;
|
||||
/** 是否必填(1是) */
|
||||
isRequired?: string;
|
||||
/** 是否唯一(1是) */
|
||||
isUnique?: string;
|
||||
/** 是否为插入字段(1是) */
|
||||
isInsert?: string;
|
||||
/** 是否编辑字段(1是) */
|
||||
isEdit?: string;
|
||||
/** 是否列表字段(1是) */
|
||||
isList?: string;
|
||||
/** 是否查询字段(1是) */
|
||||
isQuery?: string;
|
||||
/** 查询方式(等于、不等于、大于、小于、范围) */
|
||||
queryType?: string;
|
||||
/** 显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件) */
|
||||
htmlType: string;
|
||||
/** 字典类型 */
|
||||
dictType: string;
|
||||
/** 排序 */
|
||||
sort?: number;
|
||||
/** 字段大写形式 */
|
||||
capPythonField?: string;
|
||||
/** 是否主键 */
|
||||
pk?: boolean;
|
||||
/** 是否自增 */
|
||||
increment?: boolean;
|
||||
/** 是否必填 */
|
||||
required?: boolean;
|
||||
/** 是否唯一 */
|
||||
unique?: boolean;
|
||||
/** 是否为插入字段 */
|
||||
insert?: boolean;
|
||||
/** 是否编辑字段 */
|
||||
edit?: boolean;
|
||||
/** 是否列表字段 */
|
||||
list?: boolean;
|
||||
/** 是否查询字段 */
|
||||
query?: boolean;
|
||||
/** 是否为基类字段 */
|
||||
superColumn?: boolean;
|
||||
/** 是否为基类字段白名单 */
|
||||
usableColumn?: boolean;
|
||||
}
|
||||
|
||||
/** 表详情查询结果 */
|
||||
export interface GenTableDetailResult {
|
||||
/** 表信息 */
|
||||
info: GenTableOutVO;
|
||||
/** 表列信息 */
|
||||
rows: GenTableColumnUpdateSchema[];
|
||||
/** 所有表信息 */
|
||||
tables: GenTableOutVO[];
|
||||
}
|
||||
@@ -5,32 +5,21 @@ export const ResourceAPI = {
|
||||
* 获取目录列表
|
||||
* @param query 查询参数
|
||||
*/
|
||||
getResourceList(query: ResourceListQuery) {
|
||||
return request<ApiResponse<ResourceListResponse>>({
|
||||
getResourceList(query: ResourcePageQuery) {
|
||||
return request<ApiResponse<PageResult<ResourceItem[]>>>({
|
||||
url: `/monitor/resource/list`,
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 搜索资源
|
||||
* @param body 搜索条件
|
||||
*/
|
||||
searchResource(body: ResourceSearchQuery) {
|
||||
return request<ApiResponse<ResourceListResponse>>({
|
||||
url: `/monitor/resource/search`,
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param formData 文件数据
|
||||
*/
|
||||
uploadFile(formData: FormData) {
|
||||
return request<ApiResponse<UploadFilePath>>({
|
||||
return request<ApiResponse<ResourceUploadSchema>>({
|
||||
url: `/monitor/resource/upload`,
|
||||
method: "post",
|
||||
data: formData,
|
||||
@@ -111,21 +100,11 @@ export const ResourceAPI = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取资源统计信息
|
||||
*/
|
||||
getResourceStats() {
|
||||
return request<ApiResponse<ResourceStats>>({
|
||||
url: `/monitor/resource/stats`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出资源列表
|
||||
* @param body 导出条件
|
||||
*/
|
||||
exportResource(body: ResourceSearchQuery) {
|
||||
exportResource(body: ResourcePageQuery) {
|
||||
return request<Blob>({
|
||||
url: `/monitor/resource/export`,
|
||||
method: "post",
|
||||
@@ -137,31 +116,31 @@ export const ResourceAPI = {
|
||||
|
||||
export default ResourceAPI;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 资源列表查询参数
|
||||
*/
|
||||
export interface ResourceListQuery {
|
||||
/** 目录路径 */
|
||||
path: string;
|
||||
/** 递归获取 */
|
||||
recursive?: boolean;
|
||||
path?: string;
|
||||
/** 包含隐藏文件 */
|
||||
include_hidden?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源列表响应
|
||||
* 资源目录模型
|
||||
*/
|
||||
export interface ResourceListResponse {
|
||||
/** 当前路径 */
|
||||
export interface ResourceDirectorySchema {
|
||||
/** 目录路径 */
|
||||
path: string;
|
||||
/** 目录名称 */
|
||||
name: string;
|
||||
/** 文件/目录列表 */
|
||||
/** 目录项 */
|
||||
items: ResourceItem[];
|
||||
/** 总文件数 */
|
||||
/** 文件总数 */
|
||||
total_files: number;
|
||||
/** 总目录数 */
|
||||
/** 目录总数 */
|
||||
total_dirs: number;
|
||||
/** 总大小 */
|
||||
total_size: number;
|
||||
@@ -172,25 +151,33 @@ export interface ResourceListResponse {
|
||||
*/
|
||||
export interface ResourceSearchQuery {
|
||||
/** 关键词 */
|
||||
keyword?: string;
|
||||
/** 文件类型 */
|
||||
file_type?: string;
|
||||
/** 资源类型 */
|
||||
resource_type?: string;
|
||||
/** 最小文件大小 */
|
||||
min_size?: number;
|
||||
/** 最大文件大小 */
|
||||
max_size?: number;
|
||||
/** 开始时间 */
|
||||
start_date?: string;
|
||||
/** 结束时间 */
|
||||
end_date?: string;
|
||||
/** 文件扩展名 */
|
||||
extensions?: string[];
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源分页查询参数
|
||||
*/
|
||||
export interface ResourcePageQuery extends PageQuery {
|
||||
/** 关键词 */
|
||||
name?: string;
|
||||
/** 目录路径 */
|
||||
path?: string;
|
||||
/** 包含隐藏文件 */
|
||||
include_hidden?: boolean;
|
||||
/** 最大深度 */
|
||||
max_depth?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源上传响应模型
|
||||
*/
|
||||
export interface ResourceUploadSchema {
|
||||
/** 文件名 */
|
||||
filename: string;
|
||||
/** 访问URL */
|
||||
file_url: string;
|
||||
/** 文件大小 */
|
||||
file_size: number;
|
||||
/** 上传时间 */
|
||||
upload_time: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,40 +186,22 @@ export interface ResourceSearchQuery {
|
||||
export interface ResourceItem {
|
||||
/** 文件/目录名称 */
|
||||
name: string;
|
||||
/** 完整路径 */
|
||||
path: string;
|
||||
/** 文件URL路径 */
|
||||
file_url: string;
|
||||
/** 相对路径 */
|
||||
relative_path?: string;
|
||||
/** 是否为文件 */
|
||||
is_file?: boolean;
|
||||
/** 是否为目录 */
|
||||
is_dir?: boolean;
|
||||
/** 是否为目录(兼容字段) */
|
||||
is_directory?: boolean;
|
||||
/** 文件大小(字节) */
|
||||
size?: number | null;
|
||||
/** 文件类型 */
|
||||
file_type?: string | null;
|
||||
/** 文件扩展名 */
|
||||
file_extension?: string | null;
|
||||
/** 资源类型 */
|
||||
resource_type?: string;
|
||||
/** 创建时间 */
|
||||
created_time: string;
|
||||
/** 修改时间 */
|
||||
modified_time: string;
|
||||
/** 访问时间 */
|
||||
accessed_time?: string;
|
||||
/** 父路径 */
|
||||
parent_path?: string;
|
||||
/** 深度 */
|
||||
depth?: number;
|
||||
/** 是否为隐藏文件 */
|
||||
is_hidden?: boolean;
|
||||
/** 文件URL(如果是图片等可预览文件) */
|
||||
file_url?: string;
|
||||
/** 缩略图URL */
|
||||
thumbnail_url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,27 +247,3 @@ export interface ResourceCreateDirQuery {
|
||||
/** 目录名称 */
|
||||
dir_name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源统计信息
|
||||
*/
|
||||
export interface ResourceStats {
|
||||
/** 挂载点 */
|
||||
mount_point: string;
|
||||
/** 总文件数 */
|
||||
total_files: number;
|
||||
/** 总目录数 */
|
||||
total_dirs: number;
|
||||
/** 总大小(字节) */
|
||||
total_size: number;
|
||||
/** 可用空间(字节) */
|
||||
free_space: number;
|
||||
/** 已使用空间(字节) */
|
||||
used_space: number;
|
||||
/** 总空间(字节) */
|
||||
total_space: number;
|
||||
/** 按文件类型统计 */
|
||||
type_stats: Record<string, number>;
|
||||
/** 按文件扩展名统计 */
|
||||
extension_stats: Record<string, number>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import request from "@/utils/request";
|
||||
import { MenuTable } from "@/api/system/menu";
|
||||
import { MenuTable, MenuForm } from "@/api/system/menu";
|
||||
|
||||
export const UserAPI = {
|
||||
getCurrentUserInfo() {
|
||||
@@ -203,8 +203,10 @@ export interface deptTreeType {
|
||||
export interface roleSelectorType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
status?: boolean;
|
||||
description?: string;
|
||||
menus?: MenuForm[];
|
||||
}
|
||||
|
||||
export interface positionSelectorType {
|
||||
|
||||
Reference in New Issue
Block a user