OBP GwMetric 날짜시간대 변경
This commit is contained in:
@@ -1,137 +0,0 @@
|
|||||||
package com.eactive.eai.rms.onl.apim.obp;
|
|
||||||
|
|
||||||
import com.eactive.apim.portal.obp.entity.ObpGwMetric;
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ObpGwMetric JSON 응답 DTO (after-timeslice 엔드포인트용)
|
|
||||||
*
|
|
||||||
* <p>예시 출력 형식에 맞춤:</p>
|
|
||||||
* <pre>
|
|
||||||
* {
|
|
||||||
* "apiId": "1ba56b82-a9c9-41e2-b3ab-ff9377667d56",
|
|
||||||
* "apiName": "[해외송금] 가상계좌 입금실패 정보조회",
|
|
||||||
* "apiRoutingUri": "/api/obs/remittance/remittancefail*",
|
|
||||||
* "httpMethod": "POST",
|
|
||||||
* "orgId": "ORG001",
|
|
||||||
* "partnerCode": "000008-01",
|
|
||||||
* "appId": "APP001",
|
|
||||||
* "appKey": "l7xx2fd7efc523794be5a1b126873a5de613",
|
|
||||||
* "requestUri": "-1",
|
|
||||||
* "timeslice": "2025-12-02T03:00:00.000+0000",
|
|
||||||
* "attemptedCount": 3,
|
|
||||||
* "completedCount": 3
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ObpGwMetricAfterDTO {
|
|
||||||
|
|
||||||
private static final DateTimeFormatter ISO8601_MILLIS_FORMATTER =
|
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'+0000'");
|
|
||||||
|
|
||||||
@SerializedName("apiId")
|
|
||||||
private String apiId;
|
|
||||||
|
|
||||||
@SerializedName("apiName")
|
|
||||||
private String apiName;
|
|
||||||
|
|
||||||
@SerializedName("apiRoutingUri")
|
|
||||||
private String apiRoutingUri;
|
|
||||||
|
|
||||||
@SerializedName("httpMethod")
|
|
||||||
private String httpMethod;
|
|
||||||
|
|
||||||
@SerializedName("orgId")
|
|
||||||
private String orgId;
|
|
||||||
|
|
||||||
@SerializedName("partnerCode")
|
|
||||||
private String partnerCode;
|
|
||||||
|
|
||||||
@SerializedName("appId")
|
|
||||||
private String appId;
|
|
||||||
|
|
||||||
@SerializedName("appKey")
|
|
||||||
private String appKey;
|
|
||||||
|
|
||||||
@SerializedName("requestUri")
|
|
||||||
private String requestUri;
|
|
||||||
|
|
||||||
@SerializedName("timeslice")
|
|
||||||
private String timeslice;
|
|
||||||
|
|
||||||
@SerializedName("attemptedCount")
|
|
||||||
private Long attemptedCount;
|
|
||||||
|
|
||||||
@SerializedName("completedCount")
|
|
||||||
private Long completedCount;
|
|
||||||
|
|
||||||
|
|
||||||
// AS-IS 호환을 위한 필드 (null 값으로 리턴)
|
|
||||||
@SerializedName("clusterHostname")
|
|
||||||
private String clusterHostname = null;
|
|
||||||
|
|
||||||
@SerializedName("apiDatabaseId")
|
|
||||||
private Integer apiDatabaseId = null;
|
|
||||||
|
|
||||||
@SerializedName("unauthorizedCount")
|
|
||||||
private Integer authorizedCount = null;
|
|
||||||
|
|
||||||
@SerializedName("backMinTime")
|
|
||||||
private Integer backMinTime = null;
|
|
||||||
|
|
||||||
@SerializedName("backMaxTime")
|
|
||||||
private Integer backMaxTime = null;
|
|
||||||
|
|
||||||
@SerializedName("backSumTime")
|
|
||||||
private Integer backSumTime = null;
|
|
||||||
|
|
||||||
@SerializedName("frontMinTime")
|
|
||||||
private Integer frontMinTime = null;
|
|
||||||
|
|
||||||
@SerializedName("frontMaxTime")
|
|
||||||
private Integer frontMaxTime = null;
|
|
||||||
|
|
||||||
@SerializedName("frontSumTime")
|
|
||||||
private Integer frontSumTime = null;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Entity → DTO 변환
|
|
||||||
*/
|
|
||||||
public static ObpGwMetricAfterDTO from(ObpGwMetric entity) {
|
|
||||||
ObpGwMetricAfterDTO dto = new ObpGwMetricAfterDTO();
|
|
||||||
dto.setApiId(entity.getApiId());
|
|
||||||
dto.setApiName(entity.getApiName());
|
|
||||||
dto.setApiRoutingUri(entity.getUri());
|
|
||||||
dto.setHttpMethod(entity.getMethod());
|
|
||||||
dto.setOrgId(entity.getOrgId());
|
|
||||||
dto.setPartnerCode(entity.getPartnerCode());
|
|
||||||
dto.setAppId(entity.getAppId());
|
|
||||||
dto.setAppKey(entity.getClientId());
|
|
||||||
dto.setRequestUri(entity.getRequestUri());
|
|
||||||
dto.setTimeslice(formatTimeslice(entity.getTimeslice()));
|
|
||||||
dto.setAttemptedCount(entity.getAttemptedCount());
|
|
||||||
dto.setCompletedCount(entity.getCompletedCount());
|
|
||||||
|
|
||||||
|
|
||||||
// 260224 - AS-IS 개발자포탈에서 쓰이던 ID 필드 값 삭제(OBP 디비 필드 형식과 맞지 않음)
|
|
||||||
dto.setOrgId(null);
|
|
||||||
dto.setAppId(null);
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* timeslice를 ISO8601 형식 (밀리초 포함) + UTC 시간대로 변환
|
|
||||||
* 예: 2025-12-02T03:00:00.000+0000
|
|
||||||
*/
|
|
||||||
private static String formatTimeslice(LocalDateTime timeslice) {
|
|
||||||
if (timeslice == null) return null;
|
|
||||||
return timeslice.format(ISO8601_MILLIS_FORMATTER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -85,7 +85,7 @@ public class ObpGwMetricController extends BaseRestController {
|
|||||||
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
|
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
|
||||||
|
|
||||||
// 5. 데이터 조회
|
// 5. 데이터 조회
|
||||||
List<ObpGwMetricAfterDTO> metrics = obpGwMetricManService.findByTimesliceAfter(timesliceHour);
|
List<ObpGwMetricDTO> metrics = obpGwMetricManService.findByTimesliceAfter(timesliceHour);
|
||||||
|
|
||||||
// 6. JSON 변환
|
// 6. JSON 변환
|
||||||
String json = toJson(metrics, pretty);
|
String json = toJson(metrics, pretty);
|
||||||
@@ -104,90 +104,4 @@ public class ObpGwMetricController extends BaseRestController {
|
|||||||
throw new BizException("Failed to retrieve metrics: " + e.getMessage(), e);
|
throw new BizException("Failed to retrieve metrics: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 특정 시간대의 GwMetric 데이터 조회
|
|
||||||
*
|
|
||||||
* @param datetime 조회 시간대 (형식: yyyy-MM-dd_HH:mm:ss)
|
|
||||||
* @param pretty true면 JSON 들여쓰기 적용
|
|
||||||
* @return JSON Array
|
|
||||||
*/
|
|
||||||
@RequestMapping(
|
|
||||||
value = "/kjb/gw-metrics/{datetime}.json",
|
|
||||||
produces = JSON_CONTENT_TYPE
|
|
||||||
)
|
|
||||||
@ResponseBody
|
|
||||||
public String getGwMetrics(
|
|
||||||
@PathVariable("datetime") String datetime,
|
|
||||||
@RequestParam(value = "pretty", required = false) Boolean pretty) {
|
|
||||||
log.debug("GwMetric 조회 요청: datetime={}, pretty={}", datetime, pretty);
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Path 파라미터 파싱
|
|
||||||
LocalDateTime targetTime = LocalDateTime.parse(datetime, PATH_FORMATTER);
|
|
||||||
LocalDateTime timeslice = targetTime.withMinute(0).withSecond(0).withNano(0);
|
|
||||||
|
|
||||||
// 조회
|
|
||||||
List<ObpGwMetricDTO> metrics = obpGwMetricManService.findByTimeslice(timeslice);
|
|
||||||
|
|
||||||
// JSON 변환 (GSON)
|
|
||||||
String json = toJson(metrics, pretty);
|
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
|
||||||
log.info("GwMetric 조회 완료: datetime={}, count={}, size={}, elapsed={}ms",
|
|
||||||
datetime, metrics.size(), getFormattedJsonSize(json), elapsed);
|
|
||||||
|
|
||||||
return json;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("GwMetric 조회 실패: datetime={}", datetime, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 특정 시간대 이후의 GwMetric 데이터 조회
|
|
||||||
*
|
|
||||||
* @param datetime 조회 시작 시간대 (형식: yyyy-MM-dd_HH:mm:ss)
|
|
||||||
* @param pretty true면 JSON 들여쓰기 적용
|
|
||||||
* @return JSON Array (해당 시간대 이후의 모든 지표)
|
|
||||||
* @deprecated Use {@link #getGwMetricsAfterTimesliceV2(String, String, Boolean)} instead.
|
|
||||||
* Migrate to /api/metrics/afterTimeslice/{timeslice}/{providerCode}
|
|
||||||
* Will be removed in next major version.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@RequestMapping(
|
|
||||||
value = "/kjb/gw-metrics/after-timeslice/{datetime}.json",
|
|
||||||
produces = JSON_CONTENT_TYPE
|
|
||||||
)
|
|
||||||
@ResponseBody
|
|
||||||
public String getGwMetricsAfterTimeslice(
|
|
||||||
@PathVariable("datetime") String datetime,
|
|
||||||
@RequestParam(value = "pretty", required = false) Boolean pretty) {
|
|
||||||
log.debug("GwMetric after-timeslice 조회 요청: datetime={}, pretty={}", datetime, pretty);
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Path 파라미터 파싱
|
|
||||||
LocalDateTime targetTime = LocalDateTime.parse(datetime, PATH_FORMATTER);
|
|
||||||
LocalDateTime timeslice = targetTime.withMinute(0).withSecond(0).withNano(0);
|
|
||||||
|
|
||||||
// 조회
|
|
||||||
List<ObpGwMetricAfterDTO> metrics = obpGwMetricManService.findByTimesliceAfter(timeslice);
|
|
||||||
|
|
||||||
// JSON 변환 (GSON)
|
|
||||||
String json = toJson(metrics, pretty);
|
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
|
||||||
log.info("GwMetric after-timeslice 조회 완료: datetime={}, count={}, size={}, elapsed={}ms",
|
|
||||||
datetime, metrics.size(), getFormattedJsonSize(json), elapsed);
|
|
||||||
|
|
||||||
return json;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("GwMetric after-timeslice 조회 실패: datetime={}", datetime, e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,32 +8,61 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ObpGwMetric JSON 응답 DTO
|
* ObpGwMetric JSON 응답 DTO (after-timeslice 엔드포인트용)
|
||||||
* Entity 주석에 명시된 JSON Key 사용
|
*
|
||||||
|
* <p>예시 출력 형식에 맞춤:</p>
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "apiId": "1ba56b82-a9c9-41e2-b3ab-ff9377667d56",
|
||||||
|
* "apiName": "[해외송금] 가상계좌 입금실패 정보조회",
|
||||||
|
* "apiRoutingUri": "/api/obs/remittance/remittancefail*",
|
||||||
|
* "httpMethod": "POST",
|
||||||
|
* "orgId": "ORG001",
|
||||||
|
* "partnerCode": "000008-01",
|
||||||
|
* "appId": "APP001",
|
||||||
|
* "appKey": "l7xx2fd7efc523794be5a1b126873a5de613",
|
||||||
|
* "requestUri": "-1",
|
||||||
|
* "timeslice": "2025-12-02T03:00:00.000+0000",
|
||||||
|
* "attemptedCount": 3,
|
||||||
|
* "completedCount": 3
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class ObpGwMetricDTO {
|
public class ObpGwMetricDTO {
|
||||||
|
|
||||||
private static final DateTimeFormatter ISO8601_FORMATTER =
|
private static final DateTimeFormatter ISO8601_MILLIS_FORMATTER =
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'+0000'");
|
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'+0900'");
|
||||||
|
|
||||||
@SerializedName("appKey")
|
@SerializedName("apiId")
|
||||||
private String clientId;
|
private String apiId;
|
||||||
|
|
||||||
@SerializedName("clusterHostname")
|
|
||||||
private String hostname;
|
|
||||||
|
|
||||||
@SerializedName("timeslice")
|
|
||||||
private String timeslice;
|
|
||||||
|
|
||||||
@SerializedName("apiName")
|
@SerializedName("apiName")
|
||||||
private String apiName;
|
private String apiName;
|
||||||
|
|
||||||
@SerializedName("apiRoutingUri")
|
@SerializedName("apiRoutingUri")
|
||||||
private String uri;
|
private String apiRoutingUri;
|
||||||
|
|
||||||
@SerializedName("httpMethod")
|
@SerializedName("httpMethod")
|
||||||
private String method;
|
private String httpMethod;
|
||||||
|
|
||||||
|
@SerializedName("orgId")
|
||||||
|
private String orgId;
|
||||||
|
|
||||||
|
@SerializedName("partnerCode")
|
||||||
|
private String partnerCode;
|
||||||
|
|
||||||
|
@SerializedName("appId")
|
||||||
|
private String appId;
|
||||||
|
|
||||||
|
@SerializedName("appKey")
|
||||||
|
private String appKey;
|
||||||
|
|
||||||
|
@SerializedName("requestUri")
|
||||||
|
private String requestUri;
|
||||||
|
|
||||||
|
@SerializedName("timeslice")
|
||||||
|
private String timeslice;
|
||||||
|
|
||||||
@SerializedName("attemptedCount")
|
@SerializedName("attemptedCount")
|
||||||
private Long attemptedCount;
|
private Long attemptedCount;
|
||||||
@@ -41,62 +70,68 @@ public class ObpGwMetricDTO {
|
|||||||
@SerializedName("completedCount")
|
@SerializedName("completedCount")
|
||||||
private Long completedCount;
|
private Long completedCount;
|
||||||
|
|
||||||
// EAPIM ID
|
|
||||||
@SerializedName("apiIdByEapim")
|
|
||||||
private String apiId;
|
|
||||||
|
|
||||||
@SerializedName("appIdByEapim")
|
// AS-IS 호환을 위한 필드 (null 값으로 리턴)
|
||||||
private String appId;
|
@SerializedName("clusterHostname")
|
||||||
|
private String clusterHostname = null;
|
||||||
|
|
||||||
@SerializedName("orgIdByEapim")
|
@SerializedName("apiDatabaseId")
|
||||||
private String orgId;
|
private Integer apiDatabaseId = null;
|
||||||
|
|
||||||
// AS-IS 호환용 필드
|
@SerializedName("unauthorizedCount")
|
||||||
@SerializedName("appId")
|
private Integer authorizedCount = null;
|
||||||
private String appIdByCaPortal;
|
|
||||||
|
|
||||||
@SerializedName("portalOrgId")
|
@SerializedName("backMinTime")
|
||||||
private String orgIdByCaPortal;
|
private Integer backMinTime = null;
|
||||||
|
|
||||||
@SerializedName("portalApiId")
|
@SerializedName("backMaxTime")
|
||||||
private String apiIdByCaPortal;
|
private Integer backMaxTime = null;
|
||||||
|
|
||||||
@SerializedName("partnerCode")
|
@SerializedName("backSumTime")
|
||||||
private String partnerCode;
|
private Integer backSumTime = null;
|
||||||
|
|
||||||
|
@SerializedName("frontMinTime")
|
||||||
|
private Integer frontMinTime = null;
|
||||||
|
|
||||||
|
@SerializedName("frontMaxTime")
|
||||||
|
private Integer frontMaxTime = null;
|
||||||
|
|
||||||
|
@SerializedName("frontSumTime")
|
||||||
|
private Integer frontSumTime = null;
|
||||||
|
|
||||||
@SerializedName("apiId")
|
|
||||||
private String apiIdByCa;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity → DTO 변환
|
* Entity → DTO 변환
|
||||||
*/
|
*/
|
||||||
public static ObpGwMetricDTO from(ObpGwMetric entity) {
|
public static ObpGwMetricDTO from(ObpGwMetric entity) {
|
||||||
ObpGwMetricDTO dto = new ObpGwMetricDTO();
|
ObpGwMetricDTO dto = new ObpGwMetricDTO();
|
||||||
dto.setClientId(entity.getClientId());
|
dto.setApiId(entity.getApiId());
|
||||||
dto.setHostname(entity.getHostname());
|
|
||||||
dto.setTimeslice(formatTimeslice(entity.getTimeslice()));
|
|
||||||
dto.setApiName(entity.getApiName());
|
dto.setApiName(entity.getApiName());
|
||||||
dto.setUri(entity.getUri());
|
dto.setApiRoutingUri(entity.getUri());
|
||||||
dto.setMethod(entity.getMethod());
|
dto.setHttpMethod(entity.getMethod());
|
||||||
|
dto.setOrgId(entity.getOrgId());
|
||||||
|
dto.setPartnerCode(entity.getPartnerCode());
|
||||||
|
dto.setAppId(entity.getAppId());
|
||||||
|
dto.setAppKey(entity.getClientId());
|
||||||
|
dto.setRequestUri(entity.getRequestUri());
|
||||||
|
dto.setTimeslice(formatTimeslice(entity.getTimeslice()));
|
||||||
dto.setAttemptedCount(entity.getAttemptedCount());
|
dto.setAttemptedCount(entity.getAttemptedCount());
|
||||||
dto.setCompletedCount(entity.getCompletedCount());
|
dto.setCompletedCount(entity.getCompletedCount());
|
||||||
dto.setApiId(entity.getApiId());
|
|
||||||
dto.setAppId(entity.getAppId());
|
|
||||||
dto.setOrgId(entity.getOrgId());
|
// 260224 - AS-IS 개발자포탈에서 쓰이던 ID 필드 값 삭제(OBP 디비 필드 형식과 맞지 않음)
|
||||||
dto.setAppIdByCaPortal(entity.getAppIdByCaPortal());
|
dto.setOrgId(null);
|
||||||
dto.setOrgIdByCaPortal(entity.getOrgIdByCaPortal());
|
dto.setAppId(null);
|
||||||
dto.setApiIdByCaPortal(entity.getApiIdByCaPortal());
|
|
||||||
dto.setPartnerCode(entity.getPartnerCode());
|
|
||||||
dto.setApiIdByCa(entity.getApiIdByCa());
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* timeslice를 ISO8601 형식 + UTC 시간대로 변환
|
* timeslice를 ISO8601 형식 (밀리초 포함) + UTC 시간대로 변환
|
||||||
* 예: 2025-12-02T01:00:00+0000
|
* 예: 2025-12-02T03:00:00.000+0000
|
||||||
*/
|
*/
|
||||||
private static String formatTimeslice(LocalDateTime timeslice) {
|
private static String formatTimeslice(LocalDateTime timeslice) {
|
||||||
if (timeslice == null) return null;
|
if (timeslice == null) return null;
|
||||||
return timeslice.format(ISO8601_FORMATTER);
|
return timeslice.format(ISO8601_MILLIS_FORMATTER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,30 +311,17 @@ public class ObpGwMetricManService extends BaseService {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 특정 시간대의 GwMetric 데이터 조회 (REST API용)
|
|
||||||
*/
|
|
||||||
public List<ObpGwMetricDTO> findByTimeslice(LocalDateTime timeslice) {
|
|
||||||
DataSourceContextHolder.setDataSourceType(
|
|
||||||
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
|
|
||||||
|
|
||||||
List<ObpGwMetric> entities = obpGwMetricService.findByTimeslice(timeslice);
|
|
||||||
return entities.stream()
|
|
||||||
.map(ObpGwMetricDTO::from)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 특정 시간대 이후의 GwMetric 데이터 조회 (REST API용)
|
* 특정 시간대 이후의 GwMetric 데이터 조회 (REST API용)
|
||||||
* after-timeslice 엔드포인트에서 사용
|
* after-timeslice 엔드포인트에서 사용
|
||||||
*/
|
*/
|
||||||
public List<ObpGwMetricAfterDTO> findByTimesliceAfter(LocalDateTime timeslice) {
|
public List<ObpGwMetricDTO> findByTimesliceAfter(LocalDateTime timeslice) {
|
||||||
DataSourceContextHolder.setDataSourceType(
|
DataSourceContextHolder.setDataSourceType(
|
||||||
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
|
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
|
||||||
|
|
||||||
List<ObpGwMetric> entities = obpGwMetricService.findByTimesliceGreaterThanEqual(timeslice);
|
List<ObpGwMetric> entities = obpGwMetricService.findByTimesliceGreaterThanEqual(timeslice);
|
||||||
return entities.stream()
|
return entities.stream()
|
||||||
.map(ObpGwMetricAfterDTO::from)
|
.map(ObpGwMetricDTO::from)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user