feat: 新增统一数据空间与联邦查询能力
- 提供数据连接、元数据、逻辑表与关联编排能力 - 接入联邦查询、执行分析、统计估算和 SQL 补全接口 - 增加查询预算、凭据保护、租户隔离和 V61 初始化迁移
This commit is contained in:
@@ -60,6 +60,10 @@
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-module-skill</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-module-dataspace</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>tech.easyflow</groupId>
|
||||
<artifactId>easyflow-module-auth</artifactId>
|
||||
|
||||
@@ -4,8 +4,16 @@ import com.mybatisflex.core.FlexGlobalConfig;
|
||||
import com.mybatisflex.core.audit.AuditManager;
|
||||
import com.mybatisflex.core.audit.ConsoleMessageCollector;
|
||||
import com.mybatisflex.core.audit.MessageCollector;
|
||||
import com.mybatisflex.core.tenant.TenantManager;
|
||||
import com.mybatisflex.spring.boot.MyBatisFlexCustomizer;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.exception.NotWebContextException;
|
||||
import cn.dev33.satoken.exception.SaTokenContextException;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import java.math.BigInteger;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
|
||||
@Configuration
|
||||
public class MybatisConfig implements MyBatisFlexCustomizer {
|
||||
@@ -20,6 +28,9 @@ public class MybatisConfig implements MyBatisFlexCustomizer {
|
||||
flexGlobalConfig.setNormalValueOfLogicDelete(0);
|
||||
flexGlobalConfig.setDeletedValueOfLogicDelete(1);
|
||||
|
||||
// 登录请求自动追加 tenant_id 条件;系统任务没有登录上下文时保持显式全局语义。
|
||||
TenantManager.setTenantFactory(this::currentTenantIds);
|
||||
|
||||
//取消控制台的 Banner 打印
|
||||
flexGlobalConfig.setPrintBanner(false);
|
||||
|
||||
@@ -27,4 +38,24 @@ public class MybatisConfig implements MyBatisFlexCustomizer {
|
||||
MessageCollector collector = new ConsoleMessageCollector();
|
||||
AuditManager.setMessageCollector(collector);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前请求的租户条件;启动与调度线程没有 Sa-Token 上下文时保留系统级查询语义。
|
||||
*
|
||||
* @return 当前租户 ID 数组;没有请求登录上下文时返回 {@code null}
|
||||
*/
|
||||
private Object[] currentTenantIds() {
|
||||
try {
|
||||
if (!SaHolder.getContext().isValid() || !StpUtil.isLogin()) {
|
||||
return null;
|
||||
}
|
||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||
BigInteger tenantId = account == null || account.getTenantId() == null
|
||||
? BigInteger.ZERO : account.getTenantId();
|
||||
return new Object[]{tenantId};
|
||||
} catch (NotWebContextException | SaTokenContextException ignored) {
|
||||
// Bean 初始化与调度线程没有 Web 上下文,必须允许显式系统任务读取全局状态。
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE `tb_dataspace_connection` (
|
||||
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
|
||||
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '连接名称',
|
||||
`database_type` VARCHAR(32) NOT NULL COMMENT '数据库类型',
|
||||
`host` VARCHAR(255) NOT NULL COMMENT '主机地址',
|
||||
`port` INT NOT NULL COMMENT '端口',
|
||||
`database_name` VARCHAR(128) NOT NULL COMMENT '数据库名',
|
||||
`username` VARCHAR(128) NOT NULL COMMENT '用户名',
|
||||
`credential_cipher` TEXT NOT NULL COMMENT '凭据密文',
|
||||
`ssl_enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '是否启用SSL',
|
||||
`driver_class_name` VARCHAR(255) NOT NULL COMMENT 'JDBC Driver类名',
|
||||
`options_json` JSON NULL COMMENT '连接扩展选项',
|
||||
`definition_revision` BIGINT NOT NULL COMMENT 'Federation Definition revision',
|
||||
`definition_checksum` CHAR(64) NULL COMMENT 'Definition SHA-256',
|
||||
`status` VARCHAR(24) NOT NULL DEFAULT 'ENABLED' COMMENT '连接状态',
|
||||
`last_test_status` VARCHAR(24) NULL COMMENT '最近测试状态',
|
||||
`last_test_message` VARCHAR(500) NULL COMMENT '最近测试信息',
|
||||
`database_product` VARCHAR(128) NULL COMMENT '数据库产品',
|
||||
`database_version` VARCHAR(128) NULL COMMENT '数据库版本',
|
||||
`driver_name` VARCHAR(255) NULL COMMENT '驱动名称',
|
||||
`driver_version` VARCHAR(128) NULL COMMENT '驱动版本',
|
||||
`last_tested_at` DATETIME NULL COMMENT '最近测试时间',
|
||||
`metadata_refreshed_at` DATETIME NULL COMMENT '元数据刷新时间',
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
|
||||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_dataspace_connection_tenant_name` (`tenant_id`, `name`, `is_deleted`),
|
||||
KEY `idx_dataspace_connection_tenant_status` (`tenant_id`, `status`, `is_deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间物理数据库连接';
|
||||
|
||||
CREATE TABLE `tb_dataspace_object` (
|
||||
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
|
||||
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
|
||||
`connection_id` BIGINT UNSIGNED NOT NULL COMMENT '连接ID',
|
||||
`catalog_name` VARCHAR(128) NULL COMMENT '物理Catalog',
|
||||
`schema_name` VARCHAR(128) NULL COMMENT '物理Schema',
|
||||
`object_name` VARCHAR(128) NOT NULL COMMENT '表或视图名称',
|
||||
`object_type` VARCHAR(32) NOT NULL COMMENT '对象类型',
|
||||
`remarks` VARCHAR(500) NULL COMMENT '对象说明',
|
||||
`columns_json` JSON NOT NULL COMMENT '字段元数据快照',
|
||||
`primary_keys_json` JSON NOT NULL COMMENT '主键字段快照',
|
||||
`metadata_revision` BIGINT NOT NULL COMMENT '所属连接Definition revision',
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
|
||||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_dataspace_object_snapshot` (`tenant_id`, `connection_id`, `metadata_revision`, `schema_name`, `object_name`),
|
||||
KEY `idx_dataspace_object_search` (`tenant_id`, `connection_id`, `object_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间版本化数据库对象元数据';
|
||||
|
||||
CREATE TABLE `tb_dataspace` (
|
||||
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
|
||||
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '数据空间名称',
|
||||
`description` VARCHAR(500) NULL COMMENT '说明',
|
||||
`current_revision` BIGINT NOT NULL DEFAULT 0 COMMENT '当前生效revision',
|
||||
`status` VARCHAR(24) NOT NULL DEFAULT 'ENABLED' COMMENT '状态',
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
|
||||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_dataspace_tenant_name` (`tenant_id`, `name`, `is_deleted`),
|
||||
KEY `idx_dataspace_tenant_status` (`tenant_id`, `status`, `is_deleted`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Agent隔离查询使用的虚拟数据空间';
|
||||
|
||||
CREATE TABLE `tb_dataspace_revision` (
|
||||
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
|
||||
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
|
||||
`dataspace_id` BIGINT UNSIGNED NOT NULL COMMENT '数据空间ID',
|
||||
`revision_no` BIGINT NOT NULL COMMENT '不可变revision',
|
||||
`snapshot_checksum` CHAR(64) NOT NULL COMMENT '快照SHA-256',
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
|
||||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_dataspace_revision` (`tenant_id`, `dataspace_id`, `revision_no`),
|
||||
KEY `idx_dataspace_revision_lookup` (`tenant_id`, `dataspace_id`, `revision_no`, `id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间不可变revision';
|
||||
|
||||
CREATE TABLE `tb_dataspace_table_binding` (
|
||||
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
|
||||
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
|
||||
`dataspace_id` BIGINT UNSIGNED NOT NULL COMMENT '数据空间ID',
|
||||
`revision_id` BIGINT UNSIGNED NOT NULL COMMENT 'revision ID',
|
||||
`connection_id` BIGINT UNSIGNED NOT NULL COMMENT '物理连接ID',
|
||||
`source_revision` BIGINT NOT NULL COMMENT '绑定时物理源revision',
|
||||
`object_id` BIGINT UNSIGNED NOT NULL COMMENT '版本化元数据对象ID',
|
||||
`source_alias` VARCHAR(64) NOT NULL COMMENT 'Calcite数据源别名',
|
||||
`schema_alias` VARCHAR(64) NOT NULL COMMENT 'Calcite Schema别名',
|
||||
`table_alias` VARCHAR(128) NOT NULL COMMENT 'Calcite表名',
|
||||
`position_x` INT NULL COMMENT '画布横坐标',
|
||||
`position_y` INT NULL COMMENT '画布纵坐标',
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
|
||||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_dataspace_binding_object` (`tenant_id`, `revision_id`, `object_id`),
|
||||
KEY `idx_dataspace_binding_revision` (`tenant_id`, `revision_id`, `id`),
|
||||
KEY `idx_dataspace_binding_connection` (`tenant_id`, `connection_id`, `revision_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间revision表绑定';
|
||||
|
||||
CREATE TABLE `tb_dataspace_relation` (
|
||||
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
|
||||
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
|
||||
`dataspace_id` BIGINT UNSIGNED NOT NULL COMMENT '数据空间ID',
|
||||
`revision_id` BIGINT UNSIGNED NOT NULL COMMENT 'revision ID',
|
||||
`left_binding_id` BIGINT UNSIGNED NOT NULL COMMENT '左表绑定ID',
|
||||
`right_binding_id` BIGINT UNSIGNED NOT NULL COMMENT '右表绑定ID',
|
||||
`join_type` VARCHAR(16) NOT NULL COMMENT 'Join类型',
|
||||
`left_column` VARCHAR(128) NOT NULL COMMENT '左字段',
|
||||
`right_column` VARCHAR(128) NOT NULL COMMENT '右字段',
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
|
||||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_dataspace_relation_revision` (`tenant_id`, `revision_id`, `id`),
|
||||
KEY `idx_dataspace_relation_left` (`tenant_id`, `left_binding_id`),
|
||||
KEY `idx_dataspace_relation_right` (`tenant_id`, `right_binding_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间revision表关联';
|
||||
|
||||
CREATE TABLE `tb_dataspace_query_audit` (
|
||||
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
|
||||
`tenant_id` BIGINT UNSIGNED NOT NULL COMMENT '租户ID',
|
||||
`dataspace_id` BIGINT UNSIGNED NOT NULL COMMENT '数据空间ID',
|
||||
`revision_no` BIGINT NOT NULL COMMENT '查询使用的revision',
|
||||
`query_id` VARCHAR(64) NOT NULL COMMENT '查询ID',
|
||||
`query_mode` VARCHAR(32) NULL COMMENT '单源或联邦模式',
|
||||
`sql_digest` CHAR(64) NOT NULL COMMENT 'SQL SHA-256',
|
||||
`sql_text` MEDIUMTEXT NULL COMMENT '有界SQL文本',
|
||||
`status` VARCHAR(24) NOT NULL COMMENT '查询终态',
|
||||
`returned_rows` BIGINT NOT NULL DEFAULT 0 COMMENT '返回行数',
|
||||
`intermediate_rows` BIGINT NOT NULL DEFAULT 0 COMMENT '中间结果行数',
|
||||
`duration_millis` BIGINT NOT NULL DEFAULT 0 COMMENT '总耗时毫秒',
|
||||
`error_code` VARCHAR(64) NULL COMMENT '错误码',
|
||||
`error_message` VARCHAR(500) NULL COMMENT '脱敏错误信息',
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`created_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
`modified_by` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '修改人',
|
||||
`is_deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_dataspace_query_id` (`tenant_id`, `query_id`),
|
||||
KEY `idx_dataspace_query_audit` (`tenant_id`, `dataspace_id`, `created`, `id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据空间查询审计';
|
||||
|
||||
INSERT INTO `tb_sys_menu` (
|
||||
`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`,
|
||||
`is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`
|
||||
)
|
||||
SELECT
|
||||
368100000000000001, 0, 0, 'menus.ai.dataspace', '/dataspace', '/dataspace/DataspaceWorkspace', 'lucide:database-zap',
|
||||
1, '', 54, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '统一数据空间与联邦SQL工作台'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `tb_sys_menu` WHERE `id` = 368100000000000001);
|
||||
|
||||
INSERT INTO `tb_sys_menu` (`id`, `parent_id`, `menu_type`, `menu_title`, `menu_url`, `component`, `menu_icon`, `is_show`, `permission_tag`, `sort_no`, `status`, `created`, `created_by`, `modified`, `modified_by`, `remark`) VALUES
|
||||
(368100000000000011, 368100000000000001, 1, '连接查询', '', '', '', 0, '/api/v1/dataspaceConnection/query', 1, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间连接查询'),
|
||||
(368100000000000012, 368100000000000001, 1, '连接保存', '', '', '', 0, '/api/v1/dataspaceConnection/save', 2, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间连接保存'),
|
||||
(368100000000000013, 368100000000000001, 1, '连接删除', '', '', '', 0, '/api/v1/dataspaceConnection/remove', 3, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间连接删除'),
|
||||
(368100000000000014, 368100000000000001, 1, '连接测试', '', '', '', 0, '/api/v1/dataspaceConnection/test', 4, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间连接测试'),
|
||||
(368100000000000015, 368100000000000001, 1, '元数据刷新', '', '', '', 0, '/api/v1/dataspaceConnection/metadata', 5, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间元数据刷新'),
|
||||
(368100000000000021, 368100000000000001, 1, '空间查询', '', '', '', 0, '/api/v1/dataspace/query', 11, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间列表查询'),
|
||||
(368100000000000022, 368100000000000001, 1, '空间详情', '', '', '', 0, '/api/v1/dataspace/detail', 12, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间详情'),
|
||||
(368100000000000023, 368100000000000001, 1, '空间保存', '', '', '', 0, '/api/v1/dataspace/save', 13, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间保存'),
|
||||
(368100000000000024, 368100000000000001, 1, '空间删除', '', '', '', 0, '/api/v1/dataspace/remove', 14, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间删除'),
|
||||
(368100000000000031, 368100000000000001, 1, 'SQL查询', '', '', '', 0, '/api/v1/dataspaceSql/query', 21, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间SQL查询'),
|
||||
(368100000000000032, 368100000000000001, 1, 'SQL Explain', '', '', '', 0, '/api/v1/dataspaceSql/explain', 22, 0, '2026-08-22 12:00:00', 1, '2026-08-22 12:00:00', 1, '数据空间SQL Explain');
|
||||
|
||||
INSERT INTO `tb_sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES
|
||||
(368100000000000101, 1, 368100000000000001),
|
||||
(368100000000000111, 1, 368100000000000011),
|
||||
(368100000000000112, 1, 368100000000000012),
|
||||
(368100000000000113, 1, 368100000000000013),
|
||||
(368100000000000114, 1, 368100000000000014),
|
||||
(368100000000000115, 1, 368100000000000015),
|
||||
(368100000000000121, 1, 368100000000000021),
|
||||
(368100000000000122, 1, 368100000000000022),
|
||||
(368100000000000123, 1, 368100000000000023),
|
||||
(368100000000000124, 1, 368100000000000024),
|
||||
(368100000000000131, 1, 368100000000000031),
|
||||
(368100000000000132, 1, 368100000000000032);
|
||||
Reference in New Issue
Block a user