File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
package kr.itn.itnhub.mattermost;
import com.fasterxml.jackson.databind.JsonNode;
import kr.itn.itnhub.config.MattermostProperties;
import org.springframework.http.MediaType;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;
import java.net.http.HttpClient;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
@Component
public class MattermostRestClient implements MattermostClient {
private final RestClient rest;
private final String teamId;
public MattermostRestClient(MattermostProperties props) {
this.teamId = props.teamId();
// JDK HttpClient의 기본 HTTP/2 h2c 업그레이드 협상이 WireMock(Jetty) 상대로
// POST 요청에서 간헐적으로 RST_STREAM을 유발해 HTTP/1.1로 고정한다.
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.build();
this.rest = RestClient.builder()
.baseUrl(props.baseUrl())
.requestFactory(new JdkClientHttpRequestFactory(httpClient))
.defaultHeader("Authorization", "Bearer " + props.token())
.build();
}
@Override
public Optional<String> findChannelIdByInternalName(String internalName) {
try {
JsonNode body = rest.get()
.uri("/api/v4/teams/{teamId}/channels/name/{name}", teamId, internalName)
.retrieve()
.onStatus(status -> status.value() == 404, (req, res) -> {
throw new NotFound();
})
.body(JsonNode.class);
return idOf(body);
} catch (NotFound e) {
return Optional.empty();
} catch (RestClientException e) {
throw new MattermostException("채널 조회 실패: " + internalName, e);
}
}
@Override
public Optional<String> findChannelIdByDisplayName(String displayName) {
try {
JsonNode channels = rest.get()
.uri(uri -> uri.path("/api/v4/users/me/teams/{teamId}/channels")
.build(teamId))
.retrieve()
.body(JsonNode.class);
if (channels == null || !channels.isArray()) {
return Optional.empty();
}
for (JsonNode channel : channels) {
if (displayName.equals(channel.path("display_name").asText())) {
return idOf(channel);
}
}
return Optional.empty();
} catch (RestClientException e) {
throw new MattermostException("채널 목록 조회 실패", e);
}
}
@Override
public String createPrivateChannel(String internalName, String displayName) {
Map<String, String> body = new LinkedHashMap<>();
body.put("team_id", teamId);
body.put("name", internalName);
body.put("display_name", displayName);
body.put("type", "P");
body.put("purpose", "");
body.put("header", "");
try {
JsonNode created = rest.post()
.uri("/api/v4/channels")
.contentType(MediaType.APPLICATION_JSON)
.body(body)
.retrieve()
.body(JsonNode.class);
return idOf(created).orElseThrow(() ->
new MattermostException("채널 생성 응답에 id가 없습니다: " + displayName));
} catch (RestClientException e) {
throw new MattermostException("채널 생성 실패: " + displayName, e);
}
}
private Optional<String> idOf(JsonNode node) {
if (node == null) {
return Optional.empty();
}
String id = node.path("id").asText("");
return id.isBlank() ? Optional.empty() : Optional.of(id);
}
/** 404를 정상 흐름으로 되돌리기 위한 내부 신호. 밖으로 새지 않는다. */
private static final class NotFound extends RuntimeException {
}
}