readd secure avatar endpoints

This commit is contained in:
2026-03-25 22:51:29 -06:00
parent b1fe03410c
commit 9c594e7b1b
10 changed files with 343 additions and 64 deletions

View File

@@ -48,6 +48,31 @@ public class ApiClient {
return handleResponse(response, responseClass);
}
public byte[] getBytes(String path) throws Exception {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))
.GET()
.timeout(Duration.ofSeconds(30));
addAuthHeader(builder);
HttpRequest request = builder.build();
HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
int statusCode = response.statusCode();
if (statusCode == 200 || statusCode == 201) {
return response.body();
} else if (statusCode == 401) {
throw new RuntimeException("Authentication failed. Please log in again.");
} else if (statusCode == 403) {
throw new RuntimeException("Access restricted. You don't have permission to perform this action.");
} else if (statusCode == 404) {
throw new RuntimeException("Avatar not found.");
} else {
throw new RuntimeException("Request failed with status " + statusCode);
}
}
public String getRawResponse(String path) throws Exception {
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + path))

View File

@@ -26,6 +26,10 @@ public class AuthApi {
return apiClient.postMultipart("/api/v1/auth/me/avatar", "avatar", filePath, AvatarUploadResponse.class);
}
public byte[] getMyAvatarFile() throws Exception {
return apiClient.getBytes("/api/v1/auth/me/avatar/file");
}
public void deleteAvatar() throws Exception {
apiClient.delete("/api/v1/auth/me/avatar");
}

View File

@@ -18,7 +18,6 @@ import javafx.scene.paint.ImagePattern;
import javafx.scene.shape.Circle;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.ApiConfig;
import org.example.petshopdesktop.api.ChatRealtimeClient;
import org.example.petshopdesktop.api.dto.auth.AvatarUploadResponse;
import org.example.petshopdesktop.api.dto.auth.UserInfoResponse;
@@ -27,6 +26,8 @@ import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.ui.SvgWebViewFactory;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.ByteArrayInputStream;
public class MainLayoutController {
private static final String NAV_BASE_STYLE = "-fx-background-color: transparent; " +
@@ -218,8 +219,7 @@ public class MainLayoutController {
try {
AvatarUploadResponse response = AuthApi.getInstance().uploadAvatar(file.toPath());
UserSession.getInstance().setAvatarUrl(response.getAvatarUrl());
renderAvatar(UserSession.getInstance().getEmployeeName(), response.getAvatarUrl());
btnRemoveAvatar.setDisable(response.getAvatarUrl() == null || response.getAvatarUrl().isBlank());
refreshProfileHeader();
} catch (Exception e) {
ActivityLogger.getInstance().logException("MainLayoutController.btnChangeAvatarClicked", e, "Uploading avatar");
showAvatarError(e.getMessage() != null ? e.getMessage() : "Could not upload profile picture.");
@@ -263,7 +263,7 @@ public class MainLayoutController {
@FXML
public void initialize() {
logoContainer.getChildren().setAll(SvgWebViewFactory.build("/org/example/petshopdesktop/images/leons-pet-store-badge-light.svg", 94));
renderAvatar(UserSession.getInstance().getEmployeeName(), UserSession.getInstance().getAvatarUrl());
renderAvatar(UserSession.getInstance().getEmployeeName(), null);
btnRemoveAvatar.setDisable(UserSession.getInstance().getAvatarUrl() == null || UserSession.getInstance().getAvatarUrl().isBlank());
refreshProfileHeader();
applyRBAC();
@@ -285,20 +285,35 @@ public class MainLayoutController {
String displayName = userInfo.getFullName() == null || userInfo.getFullName().isBlank()
? UserSession.getInstance().getUsername()
: userInfo.getFullName();
Image avatarImage = loadAvatarImage(userInfo.getAvatarUrl());
Platform.runLater(() -> {
UserSession.getInstance().setEmployeeName(displayName);
UserSession.getInstance().setAvatarUrl(userInfo.getAvatarUrl());
lblUsername.setText(displayName);
renderAvatar(displayName, userInfo.getAvatarUrl());
renderAvatar(displayName, avatarImage);
btnRemoveAvatar.setDisable(userInfo.getAvatarUrl() == null || userInfo.getAvatarUrl().isBlank());
});
} catch (Exception e) {
Platform.runLater(() -> renderAvatar(UserSession.getInstance().getEmployeeName(), UserSession.getInstance().getAvatarUrl()));
Platform.runLater(() -> renderAvatar(UserSession.getInstance().getEmployeeName(), null));
}
}).start();
}
private void renderAvatar(String displayName, String avatarUrl) {
private Image loadAvatarImage(String avatarUrl) {
if (avatarUrl == null || avatarUrl.isBlank()) {
return null;
}
try {
byte[] imageBytes = AuthApi.getInstance().getMyAvatarFile();
Image image = new Image(new ByteArrayInputStream(imageBytes), 52, 52, true, true);
return image.isError() ? null : image;
} catch (Exception e) {
return null;
}
}
private void renderAvatar(String displayName, Image avatarImage) {
Circle border = new Circle(29);
border.setFill(Color.web("#dbe4ee"));
@@ -306,21 +321,9 @@ public class MainLayoutController {
Label initials = new Label(initials(displayName));
initials.setStyle("-fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 16px;");
if (avatarUrl != null && !avatarUrl.isBlank()) {
try {
String resolvedUrl = avatarUrl.startsWith("http") ? avatarUrl : ApiConfig.getInstance().getBaseUrl() + avatarUrl;
Image image = new Image(resolvedUrl, 52, 52, true, true, true);
if (!image.isError()) {
circle.setFill(new ImagePattern(image));
initials.setVisible(false);
} else {
circle.setFill(Color.web("#4ECDC4"));
initials.setVisible(true);
}
} catch (Exception e) {
circle.setFill(Color.web("#4ECDC4"));
initials.setVisible(true);
}
if (avatarImage != null) {
circle.setFill(new ImagePattern(avatarImage));
initials.setVisible(false);
} else {
circle.setFill(Color.web("#4ECDC4"));
initials.setVisible(true);