mirror of
http://192.168.1.205:9980/cf_devdept2/cf_imes_server.git
synced 2026-08-14 13:42:07 +08:00
报表基础模块:1、新增保存内置数据源列表(全删全增)、修改获取内置数据源列表逻辑、数据源表增加buildinType字段;
This commit is contained in:
+11
-7
@@ -1,6 +1,5 @@
|
|||||||
package com.cf.imes.module.report.controller.admin.datasource;
|
package com.cf.imes.module.report.controller.admin.datasource;
|
||||||
|
|
||||||
import com.bstek.common.config.DataSourceConfig;
|
|
||||||
import com.bstek.common.utils.MultipleJdbcTemplate;
|
import com.bstek.common.utils.MultipleJdbcTemplate;
|
||||||
import com.bstek.datasource.bean.DataSourceInfo;
|
import com.bstek.datasource.bean.DataSourceInfo;
|
||||||
import com.bstek.datasource.bean.PreviewParams;
|
import com.bstek.datasource.bean.PreviewParams;
|
||||||
@@ -47,9 +46,6 @@ public class ReportDatasourceController {
|
|||||||
@Resource
|
@Resource
|
||||||
private ReportDatasourceService datasourceService;
|
private ReportDatasourceService datasourceService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private DataSourceConfig dataSourceConfig;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private DataSourceService ureportDataSourceService;
|
private DataSourceService ureportDataSourceService;
|
||||||
|
|
||||||
@@ -117,12 +113,20 @@ public class ReportDatasourceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/buildin/datasources")
|
@GetMapping("/buildin/datasources")
|
||||||
@Operation(summary = "获取内置数据源")
|
@Operation(summary = "获取内置数据源列表")
|
||||||
// @PreAuthorize("@ss.hasPermission('report:datasource:query')")
|
// @PreAuthorize("@ss.hasPermission('report:datasource:query')")
|
||||||
public CommonResult<List<DataSourceInfo>> getBuildinDatasources() {
|
public CommonResult<List<ReportDatasourceDO>> getBuildinDatasources() {
|
||||||
return CommonResult.success(dataSourceConfig.getDatasource());
|
return CommonResult.success(datasourceService.getBuildinDatasourceList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/buildin/datasources")
|
||||||
|
@Operation(summary = "保存内置数据源")
|
||||||
|
public CommonResult<Boolean> saveBuildinDatasources(@Valid @RequestBody @NotEmpty(message = "内置数据源列表不能为空") List<ReportDatasourceSaveReqVO> reportDatasourceDOS) {
|
||||||
|
datasourceService.saveBuildinDatasources(reportDatasourceDOS);
|
||||||
|
return CommonResult.success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/datasource/connect")
|
@PostMapping("/datasource/connect")
|
||||||
@Operation(summary = "测试数据源连接")
|
@Operation(summary = "测试数据源连接")
|
||||||
// @PreAuthorize("@ss.hasPermission('report:datasource:query')")
|
// @PreAuthorize("@ss.hasPermission('report:datasource:query')")
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ public class ReportDatasourceRespVO {
|
|||||||
@Schema(description = "数据源名称", example = "测试库")
|
@Schema(description = "数据源名称", example = "测试库")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Schema(description = "数据源类型,jdbc、spring、buildin、api", example = "0")
|
@Schema(description = "数据源类型,jdbc、spring、api", example = "0")
|
||||||
private Integer type;
|
private Integer type;
|
||||||
|
|
||||||
@Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver")
|
@Schema(description = "数据源驱动类",example = "com.mysql.cj.jdbc.Driver")
|
||||||
|
|||||||
+5
-1
@@ -2,6 +2,7 @@ package com.cf.imes.module.report.controller.admin.datasource.vo;
|
|||||||
|
|
||||||
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO;
|
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO;
|
||||||
import com.cf.imes.module.report.validation.datasource.ReportDatasourceTypeInEnum;
|
import com.cf.imes.module.report.validation.datasource.ReportDatasourceTypeInEnum;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -36,10 +37,13 @@ public class ReportDatasourceSaveReqVO implements Serializable {
|
|||||||
@Schema(description = "数据源名称", example = "测试库")
|
@Schema(description = "数据源名称", example = "测试库")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Schema(description = "数据源类型,jdbc、spring、buildin、api", example = "1")
|
@Schema(description = "数据源类型,jdbc、spring、api", example = "1")
|
||||||
@ReportDatasourceTypeInEnum
|
@ReportDatasourceTypeInEnum
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private Integer buildinType;
|
||||||
|
|
||||||
@Schema(description = "spring型数据源id")
|
@Schema(description = "spring型数据源id")
|
||||||
private String beanId;
|
private String beanId;
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -8,6 +8,7 @@ import com.cf.imes.framework.mybatis.core.dataobject.BaseDO;
|
|||||||
import com.cf.imes.framework.mybatis.core.type.CompressObjectListTypeHandler;
|
import com.cf.imes.framework.mybatis.core.type.CompressObjectListTypeHandler;
|
||||||
import com.cf.imes.module.report.dal.dataobject.dataset.ReportDatasetDO;
|
import com.cf.imes.module.report.dal.dataobject.dataset.ReportDatasetDO;
|
||||||
import com.cf.imes.module.report.enums.datasource.ReportDatasourceTypeEnum;
|
import com.cf.imes.module.report.enums.datasource.ReportDatasourceTypeEnum;
|
||||||
|
import com.cf.imes.module.report.enums.template.ReportTemplateTypeEnum;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -48,10 +49,15 @@ public class ReportDatasourceDO extends BaseDO {
|
|||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模板类型,0内置、1自定义
|
* 数据源类型:jdbc、spring、api
|
||||||
*/
|
*/
|
||||||
private ReportDatasourceTypeEnum type;
|
private ReportDatasourceTypeEnum type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内置类型:0是、1否
|
||||||
|
*/
|
||||||
|
private ReportTemplateTypeEnum buildinType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spring型数据源id
|
* spring型数据源id
|
||||||
*/
|
*/
|
||||||
|
|||||||
+4
@@ -2,6 +2,7 @@ package com.cf.imes.module.report.dal.mysql.datasource;
|
|||||||
|
|
||||||
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
|
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
|
||||||
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
|
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
|
||||||
|
import org.apache.ibatis.annotations.Delete;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,4 +13,7 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface ReportDatasourceMapper extends BaseMapperX<ReportDatasourceDO> {
|
public interface ReportDatasourceMapper extends BaseMapperX<ReportDatasourceDO> {
|
||||||
|
|
||||||
|
@Delete("DELETE FROM report_datasource WHERE buildin_type = 0")
|
||||||
|
int physicsDeleteBuildinDatasource();
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -85,4 +85,18 @@ public interface ReportDatasourceService {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
List<Field> getSpringBeanResultFieldList(String clazz);
|
List<Field> getSpringBeanResultFieldList(String clazz);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内置数据源列表
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<ReportDatasourceDO> getBuildinDatasourceList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存内置数据源列表
|
||||||
|
*
|
||||||
|
* @param buildinDatasources
|
||||||
|
*/
|
||||||
|
void saveBuildinDatasources(List<ReportDatasourceSaveReqVO> buildinDatasources);
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-11
@@ -17,7 +17,10 @@ import com.cf.imes.framework.common.exception.ServiceException;
|
|||||||
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
import com.cf.imes.framework.common.util.object.BeanUtils;
|
import com.cf.imes.framework.common.util.object.BeanUtils;
|
||||||
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
|
import com.cf.imes.framework.security.core.LoginUser;
|
||||||
|
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetReqVO;
|
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetReqVO;
|
||||||
|
import com.cf.imes.module.report.controller.admin.dataset.vo.ReportDatasetSaveReqVO;
|
||||||
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportBeanDatasourceRespVO;
|
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportBeanDatasourceRespVO;
|
||||||
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceReqVO;
|
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceReqVO;
|
||||||
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO;
|
import com.cf.imes.module.report.controller.admin.datasource.vo.ReportDatasourceSaveReqVO;
|
||||||
@@ -25,6 +28,7 @@ import com.cf.imes.module.report.dal.dataobject.dataset.ReportDatasetDO;
|
|||||||
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
|
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
|
||||||
import com.cf.imes.module.report.dal.mysql.dataset.ReportDatasetMapper;
|
import com.cf.imes.module.report.dal.mysql.dataset.ReportDatasetMapper;
|
||||||
import com.cf.imes.module.report.dal.mysql.datasource.ReportDatasourceMapper;
|
import com.cf.imes.module.report.dal.mysql.datasource.ReportDatasourceMapper;
|
||||||
|
import com.cf.imes.module.report.enums.template.ReportTemplateTypeEnum;
|
||||||
import com.cf.imes.module.report.framework.ureport.annotation.CfReportSpringbeanDatasource;
|
import com.cf.imes.module.report.framework.ureport.annotation.CfReportSpringbeanDatasource;
|
||||||
import com.cf.imes.module.report.service.dataset.ReportDatasetService;
|
import com.cf.imes.module.report.service.dataset.ReportDatasetService;
|
||||||
import com.cf.imes.module.report.util.SqlInjectionUtil;
|
import com.cf.imes.module.report.util.SqlInjectionUtil;
|
||||||
@@ -55,6 +59,7 @@ import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_GET_FIE
|
|||||||
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_ILLEGAL;
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_ILLEGAL;
|
||||||
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_INJECTION_RISK;
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_INJECTION_RISK;
|
||||||
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_REQUIRED;
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASET_SQL_REQUIRED;
|
||||||
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_BUILDIN_OPERATION_PERMISSION_ERROR;
|
||||||
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_NOT_EXISTS;
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_NOT_EXISTS;
|
||||||
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_SPRINGBEAN_GET_FAIL;
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_SPRINGBEAN_GET_FAIL;
|
||||||
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_SPRINGBEAN_METHODS_GET_FAIL;
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATASOURCE_SPRINGBEAN_METHODS_GET_FAIL;
|
||||||
@@ -113,17 +118,6 @@ public class ReportDatasourceServiceImpl implements ReportDatasourceService {
|
|||||||
datasetMapper.delete(new LambdaQueryWrapperX<ReportDatasetDO>().eq(ReportDatasetDO::getDatasourceId, id));
|
datasetMapper.delete(new LambdaQueryWrapperX<ReportDatasetDO>().eq(ReportDatasetDO::getDatasourceId, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验数据源是否存在
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
private void validateDatasourceExists(Long id) {
|
|
||||||
if (datasourceMapper.selectById(id) == null) {
|
|
||||||
throw exception(DATASOURCE_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReportDatasourceDO getDatasource(Long id) {
|
public ReportDatasourceDO getDatasource(Long id) {
|
||||||
ReportDatasourceDO reportDatasourceDO = datasourceMapper.selectById(id);
|
ReportDatasourceDO reportDatasourceDO = datasourceMapper.selectById(id);
|
||||||
@@ -278,6 +272,52 @@ public class ReportDatasourceServiceImpl implements ReportDatasourceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ReportDatasourceDO> getBuildinDatasourceList() {
|
||||||
|
List<ReportDatasourceDO> reportDatasourceDOS = datasourceMapper.selectList(new LambdaQueryWrapperX<ReportDatasourceDO>()
|
||||||
|
.eqIfPresent(ReportDatasourceDO::getBuildinType, ReportTemplateTypeEnum.SYSTEM)
|
||||||
|
.orderByDesc(ReportDatasourceDO::getCreateTime));
|
||||||
|
// 查询数据集
|
||||||
|
if (CollUtil.isNotEmpty(reportDatasourceDOS)) {
|
||||||
|
reportDatasourceDOS.forEach(reportDatasourceDO -> {
|
||||||
|
queryDatasetInSource(reportDatasourceDO);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return reportDatasourceDOS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveBuildinDatasources(List<ReportDatasourceSaveReqVO> buildinDatasources) {
|
||||||
|
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||||
|
boolean isSuperAdmin = loginUser != null && loginUser.getIsSupAdmin();
|
||||||
|
// 非超管不能操作内置模板
|
||||||
|
if (!isSuperAdmin) {
|
||||||
|
throw exception(DATASOURCE_BUILDIN_OPERATION_PERMISSION_ERROR);
|
||||||
|
}
|
||||||
|
// 全删内置数据源
|
||||||
|
int deleteNum = datasourceMapper.physicsDeleteBuildinDatasource();
|
||||||
|
log.info("[ReportDatasourceService][saveBuildinDatasources]删除内置数据源{}条", deleteNum);
|
||||||
|
buildinDatasources.forEach(ds -> {
|
||||||
|
// 新增数据源
|
||||||
|
ds.setBuildinType(ReportTemplateTypeEnum.SYSTEM.getType());
|
||||||
|
ds.setId(null);
|
||||||
|
Long datasourceId = createDatasource(ds);
|
||||||
|
List<ReportDatasetSaveReqVO> datasets = ds.getDatasets();
|
||||||
|
List<ReportDatasetSaveReqVO> batchInsertDataset = new ArrayList<>();
|
||||||
|
datasets.forEach(dataset -> {
|
||||||
|
// 新增数据集
|
||||||
|
dataset.setDatasourceId(datasourceId);
|
||||||
|
dataset.setId(null);
|
||||||
|
batchInsertDataset.add(dataset);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 批量插入数据集
|
||||||
|
if (CollUtil.isNotEmpty(batchInsertDataset)) {
|
||||||
|
datasetService.batchCreateDataset(batchInsertDataset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询数据源下的数据集
|
* 查询数据源下的数据集
|
||||||
*
|
*
|
||||||
@@ -290,4 +330,15 @@ public class ReportDatasourceServiceImpl implements ReportDatasourceService {
|
|||||||
reportDatasourceDO.setDatasets(datasetList);
|
reportDatasourceDO.setDatasets(datasetList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验数据源是否存在
|
||||||
|
*
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
private void validateDatasourceExists(Long id) {
|
||||||
|
if (datasourceMapper.selectById(id) == null) {
|
||||||
|
throw exception(DATASOURCE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -69,7 +69,7 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static com.cf.imes.module.report.enums.ErrorCodeConstants.DATATEMPLATE_BUILDIN_OPERATION_PERMISSION_ERROR;
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_BUILDIN_OPERATION_PERMISSION_ERROR;
|
||||||
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_NOT_EXISTS;
|
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_NOT_EXISTS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -261,7 +261,7 @@ public class ReportTemplateServiceImpl implements ReportTemplateService {
|
|||||||
boolean isSuperAdmin = loginUser != null && loginUser.getIsSupAdmin();
|
boolean isSuperAdmin = loginUser != null && loginUser.getIsSupAdmin();
|
||||||
// 非超管不能操作内置模板
|
// 非超管不能操作内置模板
|
||||||
if (ReportTemplateTypeEnum.SYSTEM.equals(templateDO.getType()) && !isSuperAdmin) {
|
if (ReportTemplateTypeEnum.SYSTEM.equals(templateDO.getType()) && !isSuperAdmin) {
|
||||||
throw exception(DATATEMPLATE_BUILDIN_OPERATION_PERMISSION_ERROR);
|
throw exception(TEMPLATE_BUILDIN_OPERATION_PERMISSION_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ import java.lang.annotation.Target;
|
|||||||
validatedBy = {ReportDatasourceTypeInEnumValidator.class}
|
validatedBy = {ReportDatasourceTypeInEnumValidator.class}
|
||||||
)
|
)
|
||||||
public @interface ReportDatasourceTypeInEnum {
|
public @interface ReportDatasourceTypeInEnum {
|
||||||
String message() default "数据源类型[type]错误,请检查是否jdbc/spring/buildin/api";
|
String message() default "数据源类型[type]错误,请检查是否jdbc/spring/api";
|
||||||
|
|
||||||
Class<?>[] groups() default {};
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ import java.lang.annotation.Target;
|
|||||||
validatedBy = {ReportTemplateTypeInEnumValidator.class}
|
validatedBy = {ReportTemplateTypeInEnumValidator.class}
|
||||||
)
|
)
|
||||||
public @interface ReportTemplateTypeInEnum {
|
public @interface ReportTemplateTypeInEnum {
|
||||||
String message() default "类型[type]错误,请检查是否0(内置)/1(自定义)";
|
String message() default "类型错误,请检查是否0(内置)/1(自定义)";
|
||||||
|
|
||||||
Class<?>[] groups() default {};
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
|||||||
+3
@@ -21,6 +21,9 @@ public class ReportTemplateTypeInEnumValidator implements ConstraintValidator<Re
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(Integer value, ConstraintValidatorContext context) {
|
public boolean isValid(Integer value, ConstraintValidatorContext context) {
|
||||||
|
if (ObjectUtil.isNull(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
ReportTemplateTypeEnum byType = ReportTemplateTypeEnum.fromType(value);
|
ReportTemplateTypeEnum byType = ReportTemplateTypeEnum.fromType(value);
|
||||||
if (ObjectUtil.isNotNull(byType)) {
|
if (ObjectUtil.isNotNull(byType)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user