Files
board-mate/api/src/main/java/be/naaturel/boardmateapi/services/WebexService.java
2026-01-02 20:52:24 +01:00

241 lines
9.6 KiB
Java

package be.naaturel.boardmateapi.services;
import be.naaturel.boardmateapi.common.exceptions.ServiceException;
import be.naaturel.boardmateapi.common.helpers.Logger;
import be.naaturel.boardmateapi.common.models.Client;
import be.naaturel.boardmateapi.common.models.Message;
import be.naaturel.boardmateapi.common.models.Room;
import be.naaturel.boardmateapi.configurations.properties.NgrokProperties;
import be.naaturel.boardmateapi.configurations.properties.WebexProperties;
import be.naaturel.boardmateapi.repository.RoomRepo;
import be.naaturel.boardmateapi.repository.dtos.RoomDto;
import be.naaturel.boardmateapi.repository.dtos.WebexMessageDto;
import be.naaturel.boardmateapi.repository.mappings.RoomMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Service
public class WebexService {
private final String botToken;
private final String clientToken;
private final String sharedSecret;
private final String ngrokUrl;
private final RoomRepo repo;
private final ObjectMapper mapper;
@Autowired
public WebexService(
RoomRepo repo,
WebexProperties webexProperties,
NgrokProperties ngrokProperties) {
this.repo = repo;
this.botToken = webexProperties.botToken;
this.clientToken = webexProperties.clientToken;
this.sharedSecret = webexProperties.sharedSecret;
this.ngrokUrl = ngrokProperties.url;
this.mapper = new ObjectMapper();
}
@PostConstruct
public void initializeWebhooks() {
try (HttpClient client = HttpClient.newHttpClient()) {
List<Map<String, Object>> rooms = fetchRooms(client);
List<Map<String, Object>> existingWebhooks = fetchWebhooks(client);
String targetUrl = ngrokUrl + "/message/webhook";
for (Map<String, Object> room : rooms) {
String roomId = (String) room.get("id");
String roomTitle = (String) room.get("title");
if (!webhookExists(existingWebhooks, roomId, targetUrl)) {
createWebhook(client, roomId, targetUrl);
Logger.displayInfo("Webhook created for room: " + roomTitle);
} else {
Logger.displayInfo("Webhook already exists for room: " + roomTitle + "(ID:" + roomId + ")" );
}
}
} catch (Exception e) {
e.printStackTrace();
Logger.displayError("Failed to init webhooks: " + e.getMessage());
}
}
public void post(Message m) throws ServiceException {
try (HttpClient client = HttpClient.newHttpClient()) {
Room room = getRoomByClient(m.getClientId());
if (room == null) {
room = createRoom(m.getClientId());
inviteMemberToRoom(room.getId(), "laurent.crema@student.hepl.be");
}
Map<String, Object> body = Map.of(
"roomId", room.getId(),
"text", m.getContent()
);
sendPostRequest(client, "https://webexapis.com/v1/messages", botToken, body);
} catch (Exception e) {
Logger.displayError(Arrays.toString(e.getStackTrace()));
throw new ServiceException("Failed to post message");
}
}
public Message fetchMessage(String messageId) throws ServiceException {
try (HttpClient client = HttpClient.newHttpClient()) {
String response = sendGetRequest(client,
"https://webexapis.com/v1/messages/" + messageId, clientToken);
WebexMessageDto dto = mapper.readValue(response, WebexMessageDto.class);
return new Message(dto.getId(), dto.getText(), dto.getPersonId(), dto.getCreated().getEpochSecond());
} catch (Exception e) {
throw new ServiceException("Failed to fetch Webex message", e);
}
}
public Room createRoom(String clientId) throws ServiceException {
try (HttpClient client = HttpClient.newHttpClient()) {
Map<String, Object> body = Map.of("title", "Support");
String response = sendPostRequest(client, "https://webexapis.com/v1/rooms", botToken, body);
JsonNode jsonNode = mapper.readTree(response);
String id = jsonNode.get("id").asText();
String title = jsonNode.get("title").asText();
RoomDto dto = new RoomDto();
dto.setRoomId(id);
dto.setTitle(title);
dto.setClientId(clientId);
repo.save(dto);
return new Room(id, title, clientId);
} catch (Exception e) {
Logger.displayError(Arrays.toString(e.getStackTrace()));
throw new ServiceException("Failed to create room: " + e.getMessage(), e);
}
}
public void inviteMemberToRoom(String roomId, String email) throws ServiceException {
try (HttpClient client = HttpClient.newHttpClient()) {
Map<String, Object> body = Map.of(
"roomId", roomId,
"personEmail", email
);
sendPostRequest(client, "https://webexapis.com/v1/memberships", botToken, body);
} catch (Exception e) {
Logger.displayError(Arrays.toString(e.getStackTrace()));
throw new ServiceException("Failed to invite member: " + e.getMessage(), e);
}
}
public boolean verifySignature(String payload, String signature) {
try {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(sharedSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
byte[] payloadBytes = payload.getBytes(StandardCharsets.UTF_8);
byte[] rawHmac = mac.doFinal(payloadBytes);
StringBuilder sb = new StringBuilder();
for (byte b : rawHmac) {
sb.append(String.format("%02x", b));
}
String expected = sb.toString();
return expected.equals(signature.trim());
} catch (Exception e) {
Logger.displayError(e.getMessage());
return false;
}
}
public Room getRoomByClient(String clientId) {
return repo.findByClientId(clientId).map(RoomMapper::toModel).orElse(null);
}
public Room getRoomById(String roomId) {
return repo.findByRoomId(roomId).map(RoomMapper::toModel).orElse(null);
}
private List<Map<String, Object>> fetchRooms(HttpClient client) throws Exception {
String response = sendGetRequest(client, "https://webexapis.com/v1/rooms", clientToken);
JsonNode items = mapper.readTree(response).get("items");
return mapper.readValue(items.toString(), List.class);
}
private List<Map<String, Object>> fetchWebhooks(HttpClient client) throws Exception {
String response = sendGetRequest(client, "https://webexapis.com/v1/webhooks", clientToken);
JsonNode items = mapper.readTree(response).get("items");
return mapper.readValue(items.toString(), List.class);
}
private void createWebhook(HttpClient client, String roomId, String targetUrl) throws Exception {
Map<String, Object> body = Map.of(
"name", "BoardMate Webhook",
"targetUrl", targetUrl,
"resource", "messages",
"event", "created",
"roomId", roomId,
"secret", sharedSecret
);
sendPostRequest(client, "https://webexapis.com/v1/webhooks", clientToken, body);
}
private boolean webhookExists(List<Map<String, Object>> webhooks, String roomId, String targetUrl) {
return webhooks.stream().anyMatch(w ->
targetUrl.equals(w.get("targetUrl")) &&
"messages".equals(w.get("resource")) &&
"created".equals(w.get("event")) &&
roomId.equals(w.get("roomId"))
);
}
private String sendGetRequest(HttpClient client, String url, String token) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + token)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 300) {
throw new RuntimeException("Webex GET error " + response.statusCode() + " : " + response.body());
}
return response.body();
}
private String sendPostRequest(HttpClient client, String url, String token, Map<String, Object> body) throws Exception {
String jsonBody = mapper.writeValueAsString(body);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 300) {
throw new RuntimeException("Webex POST error " + response.statusCode() + " : " + response.body());
}
return response.body();
}
}