comment desktop api layer

This commit is contained in:
2026-04-20 17:06:27 -06:00
parent 4db234388a
commit 41fd7a23b9
65 changed files with 484 additions and 0 deletions

View File

@@ -1,3 +1,9 @@
/*
* Handles sending HTTP requests to the backend server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -116,6 +122,15 @@ public class ApiClient {
return handleResponse(response, responseClass);
}
/**
* Uploads a single file as a multipart/form-data POST.
* Manually builds the multipart body since Java's HttpClient doesn't have built-in support.
* @param path API endpoint path
* @param partName the form field name for the file
* @param filePath local file to upload
* @param responseClass type to deserialize the response into
* @return the parsed response
*/
public <T> T postMultipart(String path, String partName, Path filePath, Class<T> responseClass) throws Exception {
String boundary = "----PetShopDesktop" + UUID.randomUUID();
String mimeType = Files.probeContentType(filePath);
@@ -146,6 +161,17 @@ public class ApiClient {
return handleResponse(response, responseClass);
}
/**
* Uploads a file along with an optional text field in a single multipart POST.
* Uses SequenceInputStream to stream the file instead of loading everything into one byte array.
* @param path API endpoint path
* @param filePartName form field name for the file
* @param filePath local file to upload
* @param textPartName form field name for the text
* @param textContent the text value to send alongside the file
* @param responseClass type to deserialize the response into
* @return the parsed response
*/
public <T> T postMultipartWithText(String path, String filePartName, Path filePath,
String textPartName, String textContent,
Class<T> responseClass) throws Exception {
@@ -218,6 +244,11 @@ public class ApiClient {
}
}
/**
* Sends a DELETE with a JSON body (used for batch deletes).
* @param path API endpoint path
* @param requestBody object to serialize as the request body
*/
public void deleteWithBody(String path, Object requestBody) throws Exception {
String jsonBody = objectMapper.writeValueAsString(requestBody);
@@ -244,6 +275,12 @@ public class ApiClient {
}
}
/**
* Checks the HTTP status and either deserializes the body or throws a readable error.
* @param response the raw HTTP response
* @param responseClass type to deserialize into
* @return the parsed response body, or null for 204/empty
*/
private <T> T handleResponse(HttpResponse<String> response, Class<T> responseClass) throws Exception {
int statusCode = response.statusCode();
@@ -263,6 +300,13 @@ public class ApiClient {
}
}
/**
* Tries to extract a human-readable error from the JSON response body.
* Looks for an "errors" map first (field validation), then a "message" field,
* and falls back to the raw status code.
* @param response the failed HTTP response
* @return a user-facing error string
*/
private String parseErrorMessage(HttpResponse<String> response) {
try {
if (response.body() != null && !response.body().isEmpty()) {

View File

@@ -1,3 +1,9 @@
/*
* Stores the base URL and connection settings for the API.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api;
import java.io.IOException;

View File

@@ -1,3 +1,9 @@
/*
* Manages a real-time WebSocket connection for the chat feature.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api;
import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
@@ -59,6 +65,10 @@ public class ChatRealtimeClient implements WebSocket.Listener {
return INSTANCE;
}
/**
* Registers a listener that gets called when chat notification state changes.
* @param listener receives true if there are chats needing attention
*/
public void addNotificationListener(Consumer<Boolean> listener) {
synchronized (lock) {
notificationListeners.add(listener);
@@ -66,6 +76,11 @@ public class ChatRealtimeClient implements WebSocket.Listener {
}
}
/**
* Seeds the global conversation map from a full list fetch.
* Called on first load and on refresh to sync local state with server.
* @param conversations the full list from the REST API
*/
public void initializeState(List<ConversationResponse> conversations) {
synchronized (lock) {
globalConversations.clear();
@@ -76,6 +91,12 @@ public class ChatRealtimeClient implements WebSocket.Listener {
updateNotificationState();
}
/**
* Marks a conversation as replied to by updating the last sender and read state.
* Used after the current user sends a message so the badge clears.
* @param conversationId the conversation that was replied to
* @param senderId the user who sent the reply
*/
public void markConversationReplied(Long conversationId, Long senderId) {
synchronized (lock) {
ConversationResponse conv = globalConversations.get(conversationId);
@@ -100,6 +121,12 @@ public class ChatRealtimeClient implements WebSocket.Listener {
}
}
/**
* Checks if any open conversation needs staff attention.
* Two cases count: unassigned chats waiting for pickup,
* and chats assigned to me where the customer sent the last message.
* @return true if there's at least one chat needing action
*/
public boolean hasActionableChats() {
synchronized (lock) {
UserSession session = UserSession.getInstance();
@@ -125,6 +152,9 @@ public class ChatRealtimeClient implements WebSocket.Listener {
}
}
/**
* Recalculates the badge state and notifies listeners only if it changed.
*/
private void updateNotificationState() {
boolean currentState = hasActionableChats();
List<Consumer<Boolean>> listeners;
@@ -153,6 +183,10 @@ public class ChatRealtimeClient implements WebSocket.Listener {
}
}
/**
* Opens the WebSocket and sends a STOMP CONNECT frame with the JWT.
* No-ops if already connected or mid-handshake.
*/
public void connect() {
String token = UserSession.getInstance().getJwtToken();
if (token == null || token.isBlank()) {
@@ -167,6 +201,7 @@ public class ChatRealtimeClient implements WebSocket.Listener {
connecting = true;
}
// Convert the REST base URL to a WebSocket URL
String wsUrl = ApiConfig.getInstance().getBaseUrl()
.replaceFirst("^http://", "ws://")
.replaceFirst("^https://", "wss://") + "/ws/chat";
@@ -236,6 +271,13 @@ public class ChatRealtimeClient implements WebSocket.Listener {
}
}
/**
* Sends a chat message over the WebSocket using a STOMP SEND frame.
* Falls back to reconnecting if the socket isn't ready.
* @param conversationId target conversation
* @param content the message text
* @return true if the frame was sent, false if not connected
*/
public boolean sendMessage(Long conversationId, String content) {
String token = UserSession.getInstance().getJwtToken();
if (token == null || token.isBlank()) {
@@ -287,6 +329,10 @@ public class ChatRealtimeClient implements WebSocket.Listener {
applySelectedConversationSubscriptionLocked();
}
/**
* Subscribes to the currently selected conversation's message topic.
* Unsubscribes from the previous one first if it changed.
*/
private void applySelectedConversationSubscriptionLocked() {
if (webSocket == null || !connected) {
return;
@@ -324,6 +370,12 @@ public class ChatRealtimeClient implements WebSocket.Listener {
updateNotificationState();
}
/**
* Parses a single STOMP frame and dispatches it.
* Handles CONNECTED (finish handshake), MESSAGE (deliver to listeners),
* and ERROR frames.
* @param frame the raw STOMP frame text without the null terminator
*/
private void handleFrame(String frame) {
String normalized = frame.replace("\r\n", "\n");
int separator = normalized.indexOf("\n\n");
@@ -354,11 +406,14 @@ public class ChatRealtimeClient implements WebSocket.Listener {
}
if ("MESSAGE".equals(command)) {
// Look up which topic this subscription maps to
String destination;
synchronized (lock) {
destination = destinationBySubscription.get(headers.get("subscription"));
}
try {
// Messages on a specific conversation topic are chat messages;
// messages on the general conversations topic are conversation updates
if (destination != null && destination.startsWith("/topic/chat/conversations/")) {
MessageResponse message = ApiClient.getInstance().getObjectMapper().readValue(bodyPart, MessageResponse.class);
if (messageListener != null) {
@@ -371,12 +426,14 @@ public class ChatRealtimeClient implements WebSocket.Listener {
.notifyNewMessage(message.getSenderDisplayName(), message.getContent());
}
// Keep the global map in sync for badge calculations
synchronized (lock) {
ConversationResponse conv = globalConversations.get(message.getConversationId());
if (conv != null) {
conv.setLastMessage(message.getContent());
conv.setLastSenderId(message.getSenderId());
}
// Incoming message from someone else marks it unread again
if (message.getSenderId() != null && !message.getSenderId().equals(currentUserId)) {
readConversationIds.remove(message.getConversationId());
}
@@ -416,6 +473,11 @@ public class ChatRealtimeClient implements WebSocket.Listener {
}
@Override
/**
* Accumulates WebSocket text fragments and splits on the STOMP null delimiter.
* A single WebSocket message can contain partial frames, so we buffer until
* we see the \0 that marks the end of a STOMP frame.
*/
public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
synchronized (lock) {
frameBuffer.append(data);

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for an activity log entry.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.activity;
import java.time.LocalDateTime;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating an adoption.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.adoption;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for an adoption record.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.adoption;
import java.time.LocalDate;

View File

@@ -1,3 +1,9 @@
/*
* Holds daily sales total data returned from the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.analytics;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds all the summary data shown on the dashboard.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.analytics;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data for a top-selling product from the analytics endpoint.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.analytics;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating an appointment.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.appointment;
import java.time.LocalDate;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for an appointment.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.appointment;
import java.time.LocalDate;

View File

@@ -1,3 +1,9 @@
/*
* Holds the URL returned after uploading a user avatar.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.auth;
public class AvatarUploadResponse {

View File

@@ -1,3 +1,9 @@
/*
* Holds the username and password sent when logging in.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.auth;
public class LoginRequest {

View File

@@ -1,3 +1,9 @@
/*
* Holds the token and user info returned after logging in.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.auth;
public class LoginResponse {

View File

@@ -1,3 +1,9 @@
/*
* Holds the current user's profile information from the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.auth;
public class UserInfoResponse {

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when starting a new conversation.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.chat;
public class ConversationRequest {

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a conversation.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.chat;
import java.time.LocalDateTime;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when sending a chat message.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.chat;
public class MessageRequest {

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a chat message.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.chat;
import java.time.LocalDateTime;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when updating a conversation.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.chat;
public class UpdateConversationRequest {

View File

@@ -1,3 +1,9 @@
/*
* Holds a list of IDs to delete multiple records at once.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.common;
import java.util.List;

View File

@@ -1,3 +1,9 @@
/*
* Holds an ID and label pair used to fill dropdown menus.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.common;
public class DropdownOption {

View File

@@ -1,3 +1,9 @@
/*
* Holds a page of results along with pagination details.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.common;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating a coupon.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.coupon;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a coupon.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.coupon;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating an employee.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.employee;
public class EmployeeRequest {

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for an employee.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.employee;
import java.time.LocalDateTime;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when updating inventory.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.inventory;
public class InventoryRequest {

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for an inventory record.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.inventory;
import java.time.LocalDateTime;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating a pet.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.pet;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a pet.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.pet;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating a product.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.product;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a product.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.product;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when linking a product to a supplier.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.productsupplier;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a product-supplier link.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.productsupplier;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a purchase order.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.purchaseorder;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data for a single item in a sale request.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.sale;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a single sale item.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.sale;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating a sale.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.sale;
import java.util.List;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a sale.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.sale;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating a service.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.service;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a service.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.service;
import java.math.BigDecimal;

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating a supplier.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.supplier;
public class SupplierRequest {

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a supplier.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.supplier;
public class SupplierResponse {

View File

@@ -1,3 +1,9 @@
/*
* Holds data sent to the server when creating or updating a user.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.user;
public class UserRequest {

View File

@@ -1,3 +1,9 @@
/*
* Holds data returned from the server for a user.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.dto.user;
import java.time.LocalDateTime;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to fetch activity logs from the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage adoptions through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to fetch analytics and dashboard data from the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import org.example.petshopdesktop.api.ApiClient;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage appointments through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods for logging in and managing authentication.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import org.example.petshopdesktop.api.ApiClient;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage chat conversations and messages.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage coupons through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage customers through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to fetch dropdown option lists from the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage employees through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage inventory through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage pets through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage products through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage product-supplier links through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage purchase orders through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage sales through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage services through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage suppliers through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;

View File

@@ -1,3 +1,9 @@
/*
* Provides methods to manage user accounts through the server.
*
* Author: Harkamal
* Date: April 2026
*/
package org.example.petshopdesktop.api.endpoints;
import com.fasterxml.jackson.core.type.TypeReference;