Merge pull request #280 from RecentRunner/chat-fixes
Fix chat attachments and avatars
This commit was merged in pull request #280.
This commit is contained in:
@@ -24,7 +24,7 @@ public class UserAvatarController {
|
||||
}
|
||||
|
||||
@GetMapping("/{userId}/avatar/file")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public ResponseEntity<Resource> getUserAvatarFile(@PathVariable Long userId) {
|
||||
User user = userRepository.findById(userId).orElse(null);
|
||||
if (user == null || !avatarStorageService.hasAvatar(user)) {
|
||||
|
||||
@@ -22,7 +22,6 @@ import java.util.UUID;
|
||||
public class AvatarStorageService {
|
||||
|
||||
private static final String STORED_PREFIX = "/uploads/avatars/";
|
||||
private static final String OWNER_ENDPOINT = "/api/v1/auth/me/avatar/file";
|
||||
|
||||
@Value("${app.upload.base-dir:uploads}")
|
||||
private String uploadBaseDir;
|
||||
@@ -65,7 +64,7 @@ public class AvatarStorageService {
|
||||
}
|
||||
|
||||
public String toOwnerAvatarUrl(User user) {
|
||||
return hasAvatar(user) ? OWNER_ENDPOINT : null;
|
||||
return hasAvatar(user) ? "/api/v1/users/" + user.getId() + "/avatar/file" : null;
|
||||
}
|
||||
|
||||
public String toStoredAvatarUrl(String avatarFilenamePath) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
@@ -142,6 +143,38 @@ public class ApiClient {
|
||||
return handleResponse(response, responseClass);
|
||||
}
|
||||
|
||||
public <T> T postMultipartWithText(String path, String filePartName, Path filePath,
|
||||
String textPartName, String textContent,
|
||||
Class<T> responseClass) throws Exception {
|
||||
String boundary = "----PetShopDesktop" + UUID.randomUUID();
|
||||
String mimeType = Files.probeContentType(filePath);
|
||||
if (mimeType == null || mimeType.isBlank()) mimeType = "application/octet-stream";
|
||||
|
||||
byte[] fileBytes = Files.readAllBytes(filePath);
|
||||
String fileName = filePath.getFileName().toString();
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(("--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + filePartName
|
||||
+ "\"; filename=\"" + fileName + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n")
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
out.write(fileBytes);
|
||||
out.write("\r\n".getBytes(StandardCharsets.UTF_8));
|
||||
if (textContent != null && !textContent.isBlank()) {
|
||||
out.write(("--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + textPartName
|
||||
+ "\"\r\n\r\n" + textContent + "\r\n").getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
out.write(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(baseUrl + path))
|
||||
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
|
||||
.POST(HttpRequest.BodyPublishers.ofByteArray(out.toByteArray()))
|
||||
.timeout(Duration.ofSeconds(30));
|
||||
addAuthHeader(builder);
|
||||
HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
|
||||
return handleResponse(response, responseClass);
|
||||
}
|
||||
|
||||
public <T> T put(String path, Object requestBody, Class<T> responseClass) throws Exception {
|
||||
String jsonBody = objectMapper.writeValueAsString(requestBody);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.UpdateConversationRequest;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class ChatApi {
|
||||
@@ -43,6 +44,11 @@ public class ChatApi {
|
||||
return apiClient.post("/api/v1/chat/conversations/" + conversationId + "/messages", request, MessageResponse.class);
|
||||
}
|
||||
|
||||
public MessageResponse sendMessageWithAttachment(Long conversationId, File file, String content) throws Exception {
|
||||
String path = "/api/v1/chat/conversations/" + conversationId + "/attachments";
|
||||
return apiClient.postMultipartWithText(path, "file", file.toPath(), "content", content, MessageResponse.class);
|
||||
}
|
||||
|
||||
public ConversationResponse updateConversation(Long id, UpdateConversationRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/chat/conversations/" + id, request, ConversationResponse.class);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.example.petshopdesktop.api.dto.chat.UpdateConversationRequest;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
import org.example.petshopdesktop.api.endpoints.ChatApi;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -176,34 +177,33 @@ public class ChatController {
|
||||
}
|
||||
|
||||
String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim();
|
||||
if (content.isEmpty()) {
|
||||
if (selectedAttachmentFile != null) {
|
||||
lblChatStatus.setText("Attachments are not available yet");
|
||||
}
|
||||
if (content.isEmpty() && selectedAttachmentFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long convId = selectedConversation.getId();
|
||||
File attachmentFile = selectedAttachmentFile;
|
||||
|
||||
txtMessage.clear();
|
||||
btnSend.setDisable(true);
|
||||
|
||||
lblChatStatus.setText("Sending message...");
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
MessageRequest request = new MessageRequest(content);
|
||||
MessageResponse response = ChatApi.getInstance().sendMessage(convId, request);
|
||||
MessageResponse response;
|
||||
if (attachmentFile != null) {
|
||||
response = ChatApi.getInstance().sendMessageWithAttachment(convId, attachmentFile, content);
|
||||
} else {
|
||||
response = ChatApi.getInstance().sendMessage(convId, new MessageRequest(content));
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
btnSend.setDisable(false);
|
||||
realtimeClient.markConversationReplied(convId, UserSession.getInstance().getUserId());
|
||||
appendMessageIfSelected(response);
|
||||
if (selectedAttachmentFile != null) {
|
||||
if (attachmentFile != null) {
|
||||
clearLocalAttachment();
|
||||
lblChatStatus.setText("Message sent without attachment");
|
||||
} else {
|
||||
lblChatStatus.setText("Message sent");
|
||||
}
|
||||
lblChatStatus.setText("Message sent");
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
@@ -507,14 +507,16 @@ public class ChatController {
|
||||
Circle avatar = new Circle(16);
|
||||
avatar.setFill(javafx.scene.paint.Color.web(mine ? "#0f766e" : "#cbd5e1"));
|
||||
if (message.getSenderAvatarUrl() != null && !message.getSenderAvatarUrl().isBlank()) {
|
||||
try {
|
||||
String fullUrl = org.example.petshopdesktop.api.ApiConfig.getInstance().getBaseUrl() + message.getSenderAvatarUrl();
|
||||
Image img = new Image(fullUrl, true);
|
||||
img.errorProperty().addListener((obs, old, err) -> {
|
||||
if (err) avatar.setFill(javafx.scene.paint.Color.web(mine ? "#0f766e" : "#cbd5e1"));
|
||||
});
|
||||
avatar.setFill(new ImagePattern(img));
|
||||
} catch (Exception ignored) {}
|
||||
String relativeUrl = message.getSenderAvatarUrl();
|
||||
new Thread(() -> {
|
||||
try {
|
||||
byte[] bytes = ApiClient.getInstance().getBytes(relativeUrl);
|
||||
Image img = new Image(new java.io.ByteArrayInputStream(bytes));
|
||||
if (!img.isError()) {
|
||||
Platform.runLater(() -> avatar.setFill(new ImagePattern(img)));
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}).start();
|
||||
}
|
||||
|
||||
Label author = new Label(resolveAuthorLabel(message));
|
||||
@@ -532,6 +534,12 @@ public class ChatController {
|
||||
content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
bubble.getChildren().add(content);
|
||||
}
|
||||
if (message.getAttachmentName() != null && !message.getAttachmentName().isBlank()) {
|
||||
Label attachmentLabel = new Label("\uD83D\uDCCE " + message.getAttachmentName());
|
||||
attachmentLabel.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#475569") + "; -fx-font-size: 12px;");
|
||||
attachmentLabel.setWrapText(true);
|
||||
bubble.getChildren().add(attachmentLabel);
|
||||
}
|
||||
|
||||
bubble.getChildren().add(timestamp);
|
||||
bubble.setMaxWidth(420);
|
||||
|
||||
Reference in New Issue
Block a user