diff --git a/android/app/src/main/java/com/example/petstoremobile/api/InventoryApi.java b/android/app/src/main/java/com/example/petstoremobile/api/InventoryApi.java index e587b735..fa1e17b4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/InventoryApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/InventoryApi.java @@ -16,13 +16,11 @@ import retrofit2.http.Query; public interface InventoryApi { - // GET /api/v1/inventory?q=...&page=...&size=...&category=...&storeId=...&sort=... @GET("api/v1/inventory") Call> getAllInventory( @Query("page") int page, @Query("size") int size, @Query("q") String query, - @Query("category") String category, @Query("storeId") Long storeId, @Query("sort") String sort); diff --git a/android/app/src/main/java/com/example/petstoremobile/api/MessageApi.java b/android/app/src/main/java/com/example/petstoremobile/api/MessageApi.java index 29ff4ae0..13df781f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/MessageApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/MessageApi.java @@ -3,14 +3,10 @@ package com.example.petstoremobile.api; import com.example.petstoremobile.dtos.MessageDTO; import com.example.petstoremobile.dtos.SendMessageRequest; import java.util.List; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.GET; -import retrofit2.http.Multipart; import retrofit2.http.POST; -import retrofit2.http.Part; import retrofit2.http.Path; //api calls to get and send messages @@ -21,12 +17,4 @@ public interface MessageApi { @POST("api/v1/chat/conversations/{id}/messages") Call sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request); - - @Multipart - @POST("api/v1/chat/conversations/{id}/messages/attachment") - Call sendMessageWithAttachment( - @Path("id") Long conversationId, - @Part("content") RequestBody content, - @Part MultipartBody.Part file - ); } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java b/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java index e5a5a06d..ebb99139 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java @@ -13,7 +13,7 @@ public interface PurchaseOrderApi { Call> getAllPurchaseOrders( @Query("page") int page, @Query("size") int size, - @Query("query") String query, + @Query("q") String query, @Query("storeId") Long storeId, @Query("sort") String sort); diff --git a/android/app/src/main/java/com/example/petstoremobile/api/SaleApi.java b/android/app/src/main/java/com/example/petstoremobile/api/SaleApi.java index 55d1eef8..72bfd8f4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/SaleApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/SaleApi.java @@ -5,10 +5,8 @@ import com.example.petstoremobile.dtos.SaleDTO; import retrofit2.Call; import retrofit2.http.Body; -import retrofit2.http.DELETE; import retrofit2.http.GET; import retrofit2.http.POST; -import retrofit2.http.PUT; import retrofit2.http.Path; import retrofit2.http.Query; @@ -24,10 +22,4 @@ public interface SaleApi { @POST("api/v1/sales") Call createSale(@Body SaleDTO sale); - - @PUT("api/v1/sales/{id}") - Call updateSale(@Path("id") Long id, @Body SaleDTO sale); - - @DELETE("api/v1/sales/{id}") - Call deleteSale(@Path("id") Long id); } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthApi.java b/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthApi.java index 88c5312c..c0d5c1fe 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/auth/AuthApi.java @@ -1,6 +1,7 @@ package com.example.petstoremobile.api.auth; import com.example.petstoremobile.dtos.AuthDTO; +import com.example.petstoremobile.dtos.AvatarUploadResponse; import com.example.petstoremobile.dtos.UserDTO; import java.util.Map; @@ -36,7 +37,7 @@ public interface AuthApi { //upload avatar endpoint @Multipart @POST("api/v1/auth/me/avatar") - Call uploadAvatar(@Part MultipartBody.Part avatar); + Call uploadAvatar(@Part MultipartBody.Part avatar); //delete avatar endpoint @DELETE("api/v1/auth/me/avatar") diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/AvatarUploadResponse.java b/android/app/src/main/java/com/example/petstoremobile/dtos/AvatarUploadResponse.java new file mode 100644 index 00000000..194be1f4 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/AvatarUploadResponse.java @@ -0,0 +1,25 @@ +package com.example.petstoremobile.dtos; + +public class AvatarUploadResponse { + private String avatarUrl; + private String message; + + public AvatarUploadResponse() { + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java index 5ed6ac6e..b13edd67 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java @@ -9,6 +9,7 @@ import android.provider.OpenableColumns; import android.util.Log; import android.view.*; import android.view.inputmethod.EditorInfo; +import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; @@ -28,21 +29,16 @@ import com.example.petstoremobile.dtos.SendMessageRequest; import com.example.petstoremobile.models.Chat; import com.example.petstoremobile.models.Message; import com.example.petstoremobile.services.ChatNotificationService; -import com.example.petstoremobile.utils.FileUtils; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.viewmodels.ChatViewModel; import com.example.petstoremobile.websocket.StompChatManager; -import java.io.File; import java.util.*; import javax.inject.Inject; import javax.inject.Named; import dagger.hilt.android.AndroidEntryPoint; -import okhttp3.MediaType; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; @AndroidEntryPoint public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener, @@ -361,26 +357,10 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis binding.etMessage.setText(""); removeAttachment(); - try { - File file = FileUtils.getFileFromUri(requireContext(), uri); - if (file == null) return; - - String mimeType = requireContext().getContentResolver().getType(uri); - RequestBody requestFile = RequestBody.create(file, MediaType.parse(mimeType != null ? mimeType : "application/octet-stream")); - MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), requestFile); - RequestBody contentPart = RequestBody.create(text, MediaType.parse("text/plain")); - - viewModel.sendMessageWithAttachment(activeConversationId, contentPart, filePart).observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { - messageList.add(dtoToModel(resource.data)); - messageAdapter.notifyItemInserted(messageList.size() - 1); - scrollToBottom(); - loadConversations(); - } - }); - } catch (Exception e) { - Log.e(TAG, "Error sending message with attachment", e); + if (!text.isEmpty()) { + binding.etMessage.setText(text); } + Toast.makeText(requireContext(), "File attachments are not supported", Toast.LENGTH_SHORT).show(); } /** diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java index c80a82ae..af469295 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ProfileFragment.java @@ -230,9 +230,7 @@ public class ProfileFragment extends Fragment { viewModel.uploadAvatar(body).observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; if (resource.status == Resource.Status.SUCCESS) { - currentUser = resource.data; Toast.makeText(getContext(), "Avatar updated successfully", Toast.LENGTH_SHORT).show(); - // Reload image after successful upload loadProfileData(); } else if (resource.status == Resource.Status.ERROR) { Toast.makeText(getContext(), "Upload failed: " + resource.message, Toast.LENGTH_SHORT).show(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java index 378ec0bb..8da34180 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/InventoryFragment.java @@ -224,7 +224,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn } //Load all inventory items from the backend using viewModel - viewModel.getAllInventory(query, null, storeId, currentPage, PAGE_SIZE, "product.prodName").observe(getViewLifecycleOwner(), resource -> { + viewModel.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName").observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; // Check the status to see if the resource is loaded and display the data diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java index 2ec410f9..6011bac8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/AuthRepository.java @@ -7,6 +7,7 @@ import androidx.lifecycle.MutableLiveData; import com.example.petstoremobile.api.auth.AuthApi; import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.dtos.AuthDTO; +import com.example.petstoremobile.dtos.AvatarUploadResponse; import com.example.petstoremobile.dtos.UserDTO; import com.example.petstoremobile.utils.ErrorUtils; import com.example.petstoremobile.utils.Resource; @@ -83,7 +84,7 @@ public class AuthRepository extends BaseRepository { /** * Uploads a multipart image to be used as the current user's avatar. */ - public LiveData> uploadAvatar(MultipartBody.Part avatar) { + public LiveData> uploadAvatar(MultipartBody.Part avatar) { return executeCall(authApi.uploadAvatar(avatar)); } diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ChatRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ChatRepository.java index 8301797d..c3a31ad4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/ChatRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ChatRepository.java @@ -17,9 +17,6 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; - /** * Repository for handling chat-related data operations. */ @@ -58,13 +55,6 @@ public class ChatRepository extends BaseRepository { return executeCall(messageApi.sendMessage(conversationId, request)); } - /** - * Sends a message with a file attachment to a conversation. - */ - public LiveData> sendMessageWithAttachment(Long conversationId, RequestBody content, MultipartBody.Part file) { - return executeCall(messageApi.sendMessageWithAttachment(conversationId, content, file)); - } - /** * Fetches a paginated list of customers. */ diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java index 159e4cea..94526d25 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/InventoryRepository.java @@ -24,8 +24,8 @@ public class InventoryRepository extends BaseRepository { /** * Retrieves a paginated list of inventory items from the API with optional search, category, storeId and sort. */ - public LiveData>> getAllInventory(String query, String category, Long storeId, int page, int size, String sort) { - return executeCall(inventoryApi.getAllInventory(page, size, query, category, storeId, sort)); + public LiveData>> getAllInventory(String query, Long storeId, int page, int size, String sort) { + return executeCall(inventoryApi.getAllInventory(page, size, query, storeId, sort)); } /** diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java index 061ee687..36e437bb 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AuthViewModel.java @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; import com.example.petstoremobile.dtos.AuthDTO; +import com.example.petstoremobile.dtos.AvatarUploadResponse; import com.example.petstoremobile.dtos.UserDTO; import com.example.petstoremobile.repositories.AuthRepository; import com.example.petstoremobile.utils.Resource; @@ -48,7 +49,7 @@ public class AuthViewModel extends ViewModel { /** * Uploads a new avatar image for the current user. */ - public LiveData> uploadAvatar(MultipartBody.Part avatar) { + public LiveData> uploadAvatar(MultipartBody.Part avatar) { return repository.uploadAvatar(avatar); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ChatViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ChatViewModel.java index 51435f82..2b516490 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ChatViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ChatViewModel.java @@ -16,9 +16,6 @@ import java.util.List; import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; -import okhttp3.MultipartBody; -import okhttp3.RequestBody; - /** * ViewModel for managing chat-related UI state and data operations. */ @@ -52,13 +49,6 @@ public class ChatViewModel extends ViewModel { return repository.sendMessage(conversationId, request); } - /** - * Sends a message with a file attachment to a conversation. - */ - public LiveData> sendMessageWithAttachment(Long conversationId, RequestBody content, MultipartBody.Part file) { - return repository.sendMessageWithAttachment(conversationId, content, file); - } - /** * Fetches a paginated list of customers. */ diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java index cccbec89..c7ccc070 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryViewModel.java @@ -35,8 +35,8 @@ public class InventoryViewModel extends ViewModel { /** * Retrieves a paginated list of inventory items, with optional filtering and sorting. */ - public LiveData>> getAllInventory(String query, String category, Long storeId, int page, int size, String sort) { - return inventoryRepository.getAllInventory(query, category, storeId, page, size, sort); + public LiveData>> getAllInventory(String query, Long storeId, int page, int size, String sort) { + return inventoryRepository.getAllInventory(query, storeId, page, size, sort); } /** diff --git a/backend/petshop-api.postman_collection.json b/backend/petshop-api.postman_collection.json index 3b36221b..e3418fff 100644 --- a/backend/petshop-api.postman_collection.json +++ b/backend/petshop-api.postman_collection.json @@ -128,6 +128,14 @@ }, { "key": "employeeId", + "value": "3" + }, + { + "key": "myPetId", + "value": "" + }, + { + "key": "adoptionPetId", "value": "" } ], @@ -442,7 +450,7 @@ "name": "Get My Avatar File", "request": { "method": "GET", - "url": "{{baseUrl}}{{avatarUrl}}", + "url": "{{baseUrl}}/api/v1/auth/me/avatar/file", "header": [ { "key": "Authorization", @@ -837,11 +845,8 @@ "script": { "type": "text/javascript", "exec": [ - "pm.test('Status code is 200', function () {", - " pm.response.to.have.status(200);", - "});", - "pm.test('Pet image response is an image', function () {", - " pm.expect(pm.response.headers.get('Content-Type')).to.include('image/');", + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", "});" ] } @@ -1099,6 +1104,192 @@ } ] }, + { + "name": "My Pets", + "item": [ + { + "name": "List My Pets", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "url": "{{baseUrl}}/api/v1/my-pets" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Create My Pet", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "url": "{{baseUrl}}/api/v1/my-pets", + "body": { + "mode": "raw", + "raw": "{\n \"petName\": \"Postman Home Pet\",\n \"species\": \"Dog\",\n \"breed\": \"Mixed\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.customerPetId !== undefined) pm.collectionVariables.set('myPetId', jsonData.customerPetId);" + ] + } + } + ] + }, + { + "name": "Update My Pet", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "url": "{{baseUrl}}/api/v1/my-pets/{{myPetId}}", + "body": { + "mode": "raw", + "raw": "{\n \"petName\": \"Postman Home Pet Updated\",\n \"species\": \"Dog\",\n \"breed\": \"Retriever Mix\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Upload My Pet Image", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "url": "{{baseUrl}}/api/v1/my-pets/{{myPetId}}/image", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "image", + "type": "file", + "src": "{{avatarFile}}" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Delete My Pet", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "url": "{{baseUrl}}/api/v1/my-pets/{{myPetId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + } + ] + }, { "name": "Products", "item": [ @@ -2186,7 +2377,7 @@ "name": "Check Appointment Availability", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/appointments/availability?storeId={{storeId}}&serviceId={{serviceId}}&date=2026-12-20", + "url": "{{baseUrl}}/api/v1/appointments/availability?storeId=1&serviceId=1&date=2026-12-20", "header": [ { "key": "Content-Type", @@ -2341,7 +2532,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"customerId\": {{customerId}},\n \"storeId\": {{storeId}},\n \"serviceId\": {{serviceId}},\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"10:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petId\": {{petId}}\n}", + "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"employeeId\": 3,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"10:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petId\": 1\n}", "options": { "raw": { "language": "json" @@ -2383,7 +2574,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"customerId\": {{customerId}},\n \"storeId\": {{storeId}},\n \"serviceId\": {{serviceId}},\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"11:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petId\": {{petId}}\n}", + "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"employeeId\": 3,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"11:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petId\": 1\n}", "options": { "raw": { "language": "json" @@ -2501,7 +2692,9 @@ "exec": [ "pm.test('Status code is 200', function () {", " pm.response.to.have.status(200);", - "});" + "});", + "var jsonData = pm.response.json();", + "if (Array.isArray(jsonData) && jsonData.length > 0 && jsonData[0].id !== undefined) pm.collectionVariables.set('adoptionPetId', jsonData[0].id);" ] } } @@ -2587,7 +2780,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"petId\": {{adoptedPetId}},\n \"customerId\": {{customerId}},\n \"adoptionDate\": \"2026-12-21\",\n \"adoptionStatus\": \"Pending\",\n \"sourceStoreId\": {{storeId}}\n}", + "raw": "{\n \"petId\": {{adoptionPetId}},\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-21\",\n \"adoptionStatus\": \"Pending\",\n \"sourceStoreId\": 1\n}", "options": { "raw": { "language": "json" @@ -2629,7 +2822,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"petId\": {{adoptedPetId}},\n \"customerId\": {{customerId}},\n \"adoptionDate\": \"2026-12-22\",\n \"adoptionStatus\": \"Completed\",\n \"sourceStoreId\": {{storeId}}\n}", + "raw": "{\n \"petId\": {{adoptionPetId}},\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-22\",\n \"adoptionStatus\": \"Completed\",\n \"sourceStoreId\": 1\n}", "options": { "raw": { "language": "json" @@ -3263,7 +3456,7 @@ ] }, { - "name": "Customers", + "name": "Customers (Direct Users API)", "item": [ { "name": "Get Customers Dropdown", @@ -3298,6 +3491,268 @@ }, { "name": "List Customers", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users?role=CUSTOMER", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Get Customer", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users/{{customerId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Create Customer", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer\",\n \"fullName\": \"Postman Customer\",\n \"email\": \"postman.customer.{{$guid}}@example.com\",\n \"phone\": \"555-100-2000\",\n \"role\": \"CUSTOMER\",\n \"active\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.id !== undefined) pm.collectionVariables.set('customerId', jsonData.id);" + ] + } + } + ] + }, + { + "name": "Update Customer", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/users/{{customerId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer Updated\",\n \"fullName\": \"Postman Customer Updated\",\n \"email\": \"postman.customer.updated.{{$timestamp}}@example.com\",\n \"phone\": \"555-100-2001\",\n \"role\": \"CUSTOMER\",\n \"active\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Delete Customer", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users/{{customerId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create Customer For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer\",\n \"fullName\": \"Postman Customer\",\n \"email\": \"postman.bulk.customer.{{$guid}}@example.com\",\n \"phone\": \"555-100-2002\",\n \"role\": \"CUSTOMER\",\n \"active\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.id !== undefined) pm.collectionVariables.set('bulkCustomerId', jsonData.id);" + ] + } + } + ] + }, + { + "name": "Bulk Delete Customers", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n {{bulkCustomerId}}\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + } + ] + }, + { + "name": "Customers Alias (/api/v1/customers)", + "item": [ + { + "name": "List Customers - staff 200", "request": { "method": "GET", "url": "{{baseUrl}}/api/v1/customers", @@ -3328,7 +3783,106 @@ ] }, { - "name": "Get Customer", + "name": "List Customers - admin 200", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "List Customers - customer 403", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] + }, + { + "name": "Create Customer - admin 201", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"Alias\",\n \"lastName\": \"Customer\",\n \"fullName\": \"Alias Customer\",\n \"email\": \"alias.customer.{{}}@example.com\",\n \"phone\": \"555-200-3000\",\n \"role\": \"CUSTOMER\",\n \"active\": true\n}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.id !== undefined) pm.collectionVariables.set('customerId', jsonData.id);" + ] + } + } + ] + }, + { + "name": "Get Customer - staff 200", "request": { "method": "GET", "url": "{{baseUrl}}/api/v1/customers/{{customerId}}", @@ -3359,10 +3913,10 @@ ] }, { - "name": "Create Customer", + "name": "Get Customer - admin 200", "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/customers", + "method": "GET", + "url": "{{baseUrl}}/api/v1/customers/{{customerId}}", "header": [ { "key": "Content-Type", @@ -3370,19 +3924,10 @@ }, { "key": "Authorization", - "value": "Bearer {{staffToken}}", + "value": "Bearer {{adminToken}}", "type": "text" } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer\",\n \"email\": \"postman.customer.{{$guid}}@example.com\",\n \"phone\": \"555-100-2000\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } + ] }, "event": [ { @@ -3390,18 +3935,16 @@ "script": { "type": "text/javascript", "exec": [ - "pm.test('Status code is 201', function () {", - " pm.response.to.have.status(201);", - "});", - "var jsonData = pm.response.json();", - "if (jsonData.customerId !== undefined) pm.collectionVariables.set('customerId', jsonData.customerId);" + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" ] } } ] }, { - "name": "Update Customer", + "name": "Update Customer - admin 200", "request": { "method": "PUT", "url": "{{baseUrl}}/api/v1/customers/{{customerId}}", @@ -3412,18 +3955,13 @@ }, { "key": "Authorization", - "value": "Bearer {{staffToken}}", + "value": "Bearer {{adminToken}}", "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer Updated\",\n \"email\": \"postman.customer.updated.{{$timestamp}}@example.com\",\n \"phone\": \"555-100-2001\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"firstName\": \"Alias\",\n \"lastName\": \"CustomerUpdated\",\n \"fullName\": \"Alias CustomerUpdated\",\n \"email\": \"alias.customer.updated.{{}}@example.com\",\n \"phone\": \"555-200-3001\",\n \"role\": \"CUSTOMER\",\n \"active\": true\n}" } }, "event": [ @@ -3441,7 +3979,7 @@ ] }, { - "name": "Delete Customer", + "name": "Delete Customer - admin 204", "request": { "method": "DELETE", "url": "{{baseUrl}}/api/v1/customers/{{customerId}}", @@ -3452,7 +3990,7 @@ }, { "key": "Authorization", - "value": "Bearer {{staffToken}}", + "value": "Bearer {{adminToken}}", "type": "text" } ] @@ -3470,83 +4008,6 @@ } } ] - }, - { - "name": "Create Customer For Bulk Delete", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/customers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer\",\n \"email\": \"postman.customer.{{$guid}}@example.com\",\n \"phone\": \"555-100-2000\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test('Status code is 201', function () {", - " pm.response.to.have.status(201);", - "});", - "var jsonData = pm.response.json();", - "if (jsonData.customerId !== undefined) pm.collectionVariables.set('bulkCustomerId', jsonData.customerId);" - ] - } - } - ] - }, - { - "name": "Bulk Delete Customers", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/customers/bulk-delete", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n {{bulkCustomerId}}\n ]\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test('Status code is 204', function () {", - " pm.response.to.have.status(204);", - "});" - ] - } - } - ] } ] }, @@ -3738,7 +4199,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"postman_user_{{$guid}}\",\n \"password\": \"secret123\",\n \"fullName\": \"Postman User\",\n \"email\": \"postman.user.{{$guid}}@example.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}", + "raw": "{\n \"username\": \"postman_user_{{$guid}}\",\n \"password\": \"secret123\",\n \"firstName\": \"Postman\",\n \"lastName\": \"User\",\n \"fullName\": \"Postman User\",\n \"email\": \"postman.user.{{$guid}}@example.com\",\n \"role\": \"STAFF\",\n \"primaryStoreId\": 1,\n \"active\": true\n}", "options": { "raw": { "language": "json" @@ -3780,7 +4241,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"postman_user_{{$timestamp}}\",\n \"password\": \"secret123\",\n \"fullName\": \"Postman User Updated\",\n \"email\": \"postman.user.updated.{{$timestamp}}@example.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}", + "raw": "{\n \"username\": \"postman_user_{{$timestamp}}\",\n \"password\": \"secret123\",\n \"firstName\": \"Postman\",\n \"lastName\": \"User Updated\",\n \"fullName\": \"Postman User Updated\",\n \"email\": \"postman.user.updated.{{$timestamp}}@example.com\",\n \"role\": \"STAFF\",\n \"primaryStoreId\": 1,\n \"active\": true\n}", "options": { "raw": { "language": "json" @@ -3851,7 +4312,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"postman_user_{{$guid}}\",\n \"password\": \"secret123\",\n \"fullName\": \"Postman User\",\n \"email\": \"postman.user.{{$guid}}@example.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}", + "raw": "{\n \"username\": \"pb{{$timestamp}}\",\n \"password\": \"secret123\",\n \"firstName\": \"Postman\",\n \"lastName\": \"User\",\n \"fullName\": \"Postman User\",\n \"email\": \"postman.bulk.user.{{$guid}}@example.com\",\n \"role\": \"STAFF\",\n \"primaryStoreId\": 1,\n \"active\": true\n}", "options": { "raw": { "language": "json" @@ -3918,13 +4379,13 @@ ] }, { - "name": "Employees", + "name": "Employees (Direct Users API)", "item": [ { "name": "List Employees", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/employees", + "url": "{{baseUrl}}/api/v1/users?role=STAFF", "header": [ { "key": "Content-Type", @@ -3955,7 +4416,7 @@ "name": "Get Employee by ID", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/employees/{{employeeId}}", + "url": "{{baseUrl}}/api/v1/users/{{employeeId}}", "header": [ { "key": "Content-Type", @@ -3986,7 +4447,7 @@ "name": "Create Employee", "request": { "method": "POST", - "url": "{{baseUrl}}/api/v1/employees", + "url": "{{baseUrl}}/api/v1/users", "header": [ { "key": "Content-Type", @@ -4000,7 +4461,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"employee{{$timestamp}}\",\n \"password\": \"employee123\",\n \"firstName\": \"Jane\",\n \"lastName\": \"Smith\",\n \"email\": \"employee{{$timestamp}}@petshop.com\",\n \"phone\": \"403-555-0200\",\n \"role\": \"STAFF\",\n \"staffRole\": \"GROOMER\",\n \"primaryStoreId\": 1,\n \"active\": true\n}", + "raw": "{\n \"username\": \"employee{{$timestamp}}\",\n \"password\": \"employee123\",\n \"firstName\": \"Jane\",\n \"lastName\": \"Smith\",\n \"fullName\": \"Jane Smith\",\n \"email\": \"employee{{$timestamp}}@petshop.com\",\n \"phone\": \"403-555-0200\",\n \"role\": \"STAFF\",\n \"staffRole\": \"GROOMER\",\n \"primaryStoreId\": 1,\n \"active\": true\n}", "options": { "raw": { "language": "json" @@ -4018,7 +4479,7 @@ " pm.response.to.have.status(201);", "});", "var jsonData = pm.response.json();", - "if (jsonData.id) pm.collectionVariables.set('employeeId', jsonData.id);" + "if (jsonData.id !== undefined) pm.collectionVariables.set('employeeId', jsonData.id);" ] } } @@ -4028,7 +4489,7 @@ "name": "Update Employee", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/employees/{{employeeId}}", + "url": "{{baseUrl}}/api/v1/users/{{employeeId}}", "header": [ { "key": "Content-Type", @@ -4042,7 +4503,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"firstName\": \"Jane\",\n \"lastName\": \"Smith\",\n \"email\": \"employee.updated@petshop.com\",\n \"phone\": \"403-555-0201\",\n \"role\": \"STAFF\",\n \"staffRole\": \"VET\",\n \"primaryStoreId\": 1,\n \"active\": true\n}", + "raw": "{\n \"firstName\": \"Jane\",\n \"lastName\": \"Smith\",\n \"fullName\": \"Jane Smith\",\n \"email\": \"employee.updated{{$timestamp}}@petshop.com\",\n \"phone\": \"403-555-0201\",\n \"role\": \"STAFF\",\n \"staffRole\": \"VET\",\n \"primaryStoreId\": 1,\n \"active\": true\n}", "options": { "raw": { "language": "json" @@ -4066,6 +4527,207 @@ }, { "name": "Delete Employee", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users/{{employeeId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + } + ] + }, + { + "name": "Employees Alias (/api/v1/employees)", + "item": [ + { + "name": "List Employees - admin 200", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/employees", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "List Employees - staff 403", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/employees", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] + }, + { + "name": "Create Employee - admin 201", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/employees", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"aliasemployee{{}}\",\n \"password\": \"Test1234!\",\n \"firstName\": \"Alias\",\n \"lastName\": \"Employee\",\n \"fullName\": \"Alias Employee\",\n \"email\": \"alias.employee.{{}}@petshop.com\",\n \"phone\": \"403-555-0300\",\n \"role\": \"STAFF\",\n \"staffRole\": \"GROOMER\",\n \"primaryStoreId\": 1,\n \"active\": true\n}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.id !== undefined) pm.collectionVariables.set('employeeId', jsonData.id);" + ] + } + } + ] + }, + { + "name": "Get Employee - admin 200", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/employees/{{employeeId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Update Employee - admin 200", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/employees/{{employeeId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"Alias\",\n \"lastName\": \"EmployeeUpdated\",\n \"fullName\": \"Alias EmployeeUpdated\",\n \"email\": \"alias.employee.updated.{{}}@petshop.com\",\n \"phone\": \"403-555-0301\",\n \"role\": \"STAFF\",\n \"staffRole\": \"VET\",\n \"primaryStoreId\": 1,\n \"active\": true\n}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Delete Employee - admin 204", "request": { "method": "DELETE", "url": "{{baseUrl}}/api/v1/employees/{{employeeId}}", @@ -4505,11 +5167,53 @@ } ] }, + { + "name": "Create Inventory", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/inventory", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"prodId\": 1,\n \"quantity\": 10,\n \"storeId\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.inventoryId !== undefined) pm.collectionVariables.set('inventoryId', jsonData.inventoryId);" + ] + } + } + ] + }, { "name": "Get Inventory Item", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/inventory/1", + "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", "header": [ { "key": "Content-Type", @@ -4536,48 +5240,6 @@ } ] }, - { - "name": "Create Inventory", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/inventory", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"prodId\": {{productId}},\n \"quantity\": 10,\n \"storeId\": {{storeId}}\n}", - "options": { - "raw": { - "language": "json" - } - } - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test('Status code is 201', function () {", - " pm.response.to.have.status(201);", - "});", - "var jsonData = pm.response.json();", - "if (jsonData.inventoryId !== undefined) pm.collectionVariables.set('inventoryId', jsonData.inventoryId);" - ] - } - } - ] - }, { "name": "Update Inventory", "request": { @@ -4596,7 +5258,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"prodId\": {{productId}},\n \"quantity\": 12,\n \"storeId\": {{storeId}}\n}", + "raw": "{\n \"prodId\": 1,\n \"quantity\": 12,\n \"storeId\": 1\n}", "options": { "raw": { "language": "json" @@ -4667,7 +5329,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"prodId\": {{productId}},\n \"quantity\": 10,\n \"storeId\": {{storeId}}\n}", + "raw": "{\n \"prodId\": 1,\n \"quantity\": 10,\n \"storeId\": 1\n}", "options": { "raw": { "language": "json" @@ -4898,8 +5560,8 @@ "script": { "type": "text/javascript", "exec": [ - "pm.test('Status code is 403', function () {", - " pm.response.to.have.status(403);", + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", "});" ] } @@ -4933,8 +5595,8 @@ "script": { "type": "text/javascript", "exec": [ - "pm.test('Status code is 403', function () {", - " pm.response.to.have.status(403);", + "pm.test('Status code is 400', function () {", + " pm.response.to.have.status(400);", "});" ] } @@ -5129,6 +5791,46 @@ } ] }, + { + "name": "Create Product Supplier", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"productId\": 1,\n \"supplierId\": 1,\n \"cost\": 7.50\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});" + ] + } + } + ] + }, { "name": "Get Product Supplier", "request": { @@ -5160,51 +5862,11 @@ } ] }, - { - "name": "Create Product Supplier", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/product-suppliers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"productId\": {{productId}},\n \"supplierId\": {{supplierId}},\n \"cost\": 7.50\n}", - "options": { - "raw": { - "language": "json" - } - } - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test('Status code is 201', function () {", - " pm.response.to.have.status(201);", - "});" - ] - } - } - ] - }, { "name": "Update Product Supplier", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/product-suppliers/{{productId}}/{{supplierId}}", + "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", "header": [ { "key": "Content-Type", @@ -5218,7 +5880,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"productId\": {{productId}},\n \"supplierId\": {{supplierId}},\n \"cost\": 7.75\n}", + "raw": "{\n \"productId\": 1,\n \"supplierId\": 1,\n \"cost\": 7.75\n}", "options": { "raw": { "language": "json" @@ -5244,7 +5906,7 @@ "name": "Delete Product Supplier", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/product-suppliers/{{productId}}/{{supplierId}}", + "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", "header": [ { "key": "Content-Type", @@ -5289,7 +5951,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"productId\": {{productId}},\n \"supplierId\": {{supplierId}},\n \"cost\": 7.50\n}", + "raw": "{\n \"productId\": 1,\n \"supplierId\": 1,\n \"cost\": 7.50\n}", "options": { "raw": { "language": "json" @@ -5329,7 +5991,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keys\": [\n {\"productId\": {{productId}}, \"supplierId\": {{supplierId}}}\n ]\n}" + "raw": "{\n \"keys\": [\n {\"productId\": 1, \"supplierId\": 1}\n ]\n}" } }, "event": [ diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java index c237f27e..ec4f822b 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -13,6 +13,7 @@ import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.AvatarStorageService; import com.petshop.backend.util.AuthenticationHelper; +import com.petshop.backend.util.PhoneUtils; import jakarta.validation.Valid; import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; @@ -53,19 +54,23 @@ public class AuthController { @PostMapping("/register") public ResponseEntity register(@Valid @RequestBody RegisterRequest request) { - if (userRepository.findByUsername(request.getUsername()).isPresent()) { + String username = trimToNull(request.getUsername()); + String email = trimToNull(request.getEmail()); + NameParts nameParts = splitFullName(request.getFullName()); + String phone = normalizePhone(request.getPhone()); + + if (userRepository.findByUsername(username).isPresent()) { Map error = new HashMap<>(); error.put("message", "Username already exists"); return ResponseEntity.status(HttpStatus.CONFLICT).body(error); } - if (userRepository.findByEmail(request.getEmail()).isPresent()) { + if (userRepository.findByEmail(email).isPresent()) { Map error = new HashMap<>(); error.put("message", "Email already exists"); return ResponseEntity.status(HttpStatus.CONFLICT).body(error); } - String phone = trimToNull(request.getPhone()); if (phone != null && userRepository.findByPhone(phone).isPresent()) { Map error = new HashMap<>(); error.put("message", "Phone already exists"); @@ -73,10 +78,12 @@ public class AuthController { } User user = new User(); - user.setUsername(request.getUsername()); + user.setUsername(username); user.setPassword(passwordEncoder.encode(request.getPassword())); - user.setEmail(request.getEmail()); - user.setFullName(request.getFullName()); + user.setEmail(email); + user.setFirstName(nameParts.firstName()); + user.setLastName(nameParts.lastName()); + user.setFullName(nameParts.fullName()); user.setPhone(phone); user.setRole(User.Role.CUSTOMER); user.setActive(true); @@ -143,31 +150,36 @@ public class AuthController { User user = getAuthenticatedUser(); boolean invalidateToken = false; - if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) { - if (userRepository.findByUsername(request.getUsername()).isPresent()) { + String username = trimToNull(request.getUsername()); + if (username != null && !username.equals(user.getUsername())) { + if (userRepository.findByUsername(username).isPresent()) { Map error = new HashMap<>(); error.put("message", "Username already exists"); return ResponseEntity.status(HttpStatus.CONFLICT).body(error); } - user.setUsername(request.getUsername()); + user.setUsername(username); invalidateToken = true; } - if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) { - if (userRepository.findByEmail(request.getEmail()).isPresent()) { + String email = trimToNull(request.getEmail()); + if (email != null && !email.equals(user.getEmail())) { + if (userRepository.findByEmail(email).isPresent()) { Map error = new HashMap<>(); error.put("message", "Email already exists"); return ResponseEntity.status(HttpStatus.CONFLICT).body(error); } - user.setEmail(request.getEmail()); + user.setEmail(email); } if (request.getFullName() != null) { - user.setFullName(request.getFullName()); + NameParts nameParts = splitFullName(request.getFullName()); + user.setFirstName(nameParts.firstName()); + user.setLastName(nameParts.lastName()); + user.setFullName(nameParts.fullName()); } if (request.getPhone() != null) { - String phone = trimToNull(request.getPhone()); + String phone = normalizePhone(request.getPhone()); if (!java.util.Objects.equals(phone, user.getPhone())) { if (phone != null && userRepository.findByPhone(phone) .filter(existing -> !existing.getId().equals(user.getId())) @@ -196,11 +208,15 @@ public class AuthController { private UserInfoResponse toUserInfoResponse(User user) { StoreLocation primaryStore = user.getPrimaryStore(); Long customerId = user.getRole() == User.Role.CUSTOMER ? user.getId() : null; + String fullName = user.getFullName(); + if (fullName == null || fullName.isBlank()) { + fullName = joinFullName(user.getFirstName(), user.getLastName()); + } return new UserInfoResponse( user.getId(), user.getUsername(), user.getEmail(), - user.getFullName(), + fullName, user.getPhone(), avatarStorageService.toOwnerAvatarUrl(user), user.getRole().name(), @@ -218,6 +234,36 @@ public class AuthController { return trimmed.isEmpty() ? null : trimmed; } + private String normalizePhone(String value) { + return trimToNull(PhoneUtils.normalize(trimToNull(value))); + } + + private NameParts splitFullName(String value) { + String normalized = trimToNull(value); + if (normalized == null) { + throw new IllegalArgumentException("Full name is required"); + } + String[] parts = normalized.split("\\s+", 2); + String firstName = parts[0]; + String lastName = parts.length > 1 ? parts[1] : ""; + return new NameParts(firstName, lastName, joinFullName(firstName, lastName)); + } + + private String joinFullName(String firstName, String lastName) { + String first = trimToNull(firstName); + String last = trimToNull(lastName); + if (first == null) { + return last == null ? null : last; + } + if (last == null) { + return first; + } + return first + " " + last; + } + + private record NameParts(String firstName, String lastName, String fullName) { + } + @PostMapping("/me/avatar") public ResponseEntity uploadAvatar(@RequestParam("avatar") MultipartFile file) { User user = getAuthenticatedUser(); diff --git a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java b/backend/src/main/java/com/petshop/backend/controller/DropdownController.java index 4d995cd4..3b0a2009 100644 --- a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/backend/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -153,6 +153,16 @@ public class DropdownController { ); } + @GetMapping("/customers/{customerId}/pets") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") + public ResponseEntity> getCustomerPets(@PathVariable Long customerId) { + return ResponseEntity.ok( + petRepository.findAllByOwner_IdOrderByPetNameAsc(customerId).stream() + .map(p -> new DropdownOption(p.getPetId(), p.getPetName())) + .collect(Collectors.toList()) + ); + } + @GetMapping("/suppliers") @PreAuthorize("hasRole('ADMIN')") public ResponseEntity> getSuppliers() { diff --git a/backend/src/main/java/com/petshop/backend/controller/MyPetController.java b/backend/src/main/java/com/petshop/backend/controller/MyPetController.java new file mode 100644 index 00000000..e43dbc42 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/MyPetController.java @@ -0,0 +1,76 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.pet.MyPetRequest; +import com.petshop.backend.dto.pet.MyPetResponse; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.service.PetService; +import com.petshop.backend.util.AuthenticationHelper; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/my-pets") +@PreAuthorize("isAuthenticated()") +public class MyPetController { + + private final PetService petService; + private final UserRepository userRepository; + + public MyPetController(PetService petService, UserRepository userRepository) { + this.petService = petService; + this.userRepository = userRepository; + } + + @GetMapping + public ResponseEntity> getMyPets() { + return ResponseEntity.ok(petService.getMyPets(currentUserId())); + } + + @PostMapping + public ResponseEntity createMyPet(@Valid @RequestBody MyPetRequest request) { + return ResponseEntity.ok(petService.createMyPet(currentUserId(), request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateMyPet(@PathVariable Long id, @Valid @RequestBody MyPetRequest request) { + return ResponseEntity.ok(petService.updateMyPet(currentUserId(), id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteMyPet(@PathVariable Long id) { + petService.deleteMyPet(currentUserId(), id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/{id}/image") + public ResponseEntity uploadMyPetImage(@PathVariable Long id, @RequestParam("image") MultipartFile image) { + try { + return ResponseEntity.ok(petService.uploadMyPetImage(currentUserId(), id, image)); + } catch (IllegalArgumentException ex) { + return ResponseEntity.badRequest().body(Map.of("message", ex.getMessage())); + } catch (IOException ex) { + return ResponseEntity.badRequest().body(Map.of("message", "Failed to upload pet image: " + ex.getMessage())); + } + } + + private Long currentUserId() { + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + return user.getId(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/PetImageController.java b/backend/src/main/java/com/petshop/backend/controller/PetImageController.java index bd9717ca..93477cc7 100644 --- a/backend/src/main/java/com/petshop/backend/controller/PetImageController.java +++ b/backend/src/main/java/com/petshop/backend/controller/PetImageController.java @@ -48,12 +48,8 @@ public class PetImageController { @GetMapping("/{id}/image") public ResponseEntity getPetImage(@PathVariable Long id) { - try { - PetService.ImagePayload payload = petService.loadPetImage(id, currentUserId(), currentUserRole()); - return ResponseEntity.ok().contentType(payload.mediaType()).body(payload.resource()); - } catch (PetService.ForbiddenImageAccessException ex) { - return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); - } + PetService.ImagePayload payload = petService.loadPetImage(id, currentUserId(), currentUserRole()); + return ResponseEntity.ok().contentType(payload.mediaType()).body(payload.resource()); } @DeleteMapping("/{id}/image") diff --git a/backend/src/main/java/com/petshop/backend/controller/RefundController.java b/backend/src/main/java/com/petshop/backend/controller/RefundController.java index bd6f158b..0001df1e 100644 --- a/backend/src/main/java/com/petshop/backend/controller/RefundController.java +++ b/backend/src/main/java/com/petshop/backend/controller/RefundController.java @@ -15,9 +15,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; -import java.util.HashMap; import java.util.List; -import java.util.Map; @RestController @RequestMapping("/api/v1/refunds") @@ -33,27 +31,20 @@ public class RefundController { @PostMapping @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") - public ResponseEntity createRefund(@Valid @RequestBody RefundRequest request) { - try { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String role = authentication.getAuthorities().stream() - .findFirst() - .map(authority -> authority.getAuthority().replace("ROLE_", "")) - .orElse(null); + public ResponseEntity createRefund(@Valid @RequestBody RefundRequest request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); - Long customerId = null; - if (role != null && role.equals("CUSTOMER")) { - User user = AuthenticationHelper.getAuthenticatedUser(userRepository); - customerId = user.getId(); - } - - RefundResponse refund = refundService.createRefund(request, customerId); - return ResponseEntity.status(HttpStatus.CREATED).body(refund); - } catch (RuntimeException e) { - Map error = new HashMap<>(); - error.put("message", e.getMessage()); - return ResponseEntity.badRequest().body(error); + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } + + return ResponseEntity.status(HttpStatus.CREATED).body(refundService.createRefund(request, customerId)); } @GetMapping @@ -77,54 +68,32 @@ public class RefundController { @GetMapping("/{id}") @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") - public ResponseEntity getRefundById(@PathVariable Long id) { - try { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String role = authentication.getAuthorities().stream() - .findFirst() - .map(authority -> authority.getAuthority().replace("ROLE_", "")) - .orElse(null); + public ResponseEntity getRefundById(@PathVariable Long id) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); - Long customerId = null; - if (role != null && role.equals("CUSTOMER")) { - User user = AuthenticationHelper.getAuthenticatedUser(userRepository); - customerId = user.getId(); - } - - RefundResponse refund = refundService.getRefundById(id, customerId); - return ResponseEntity.ok(refund); - } catch (RuntimeException e) { - Map error = new HashMap<>(); - error.put("message", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + customerId = user.getId(); } + + return ResponseEntity.ok(refundService.getRefundById(id, customerId)); } @PutMapping("/{id}") @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") - public ResponseEntity updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) { - try { - RefundResponse refund = refundService.updateRefundStatus(id, request.getStatus()); - return ResponseEntity.ok(refund); - } catch (RuntimeException e) { - Map error = new HashMap<>(); - error.put("message", e.getMessage()); - return ResponseEntity.badRequest().body(error); - } + public ResponseEntity updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) { + return ResponseEntity.ok(refundService.updateRefundStatus(id, request.getStatus())); } @DeleteMapping("/{id}") @PreAuthorize("hasRole('ADMIN')") - public ResponseEntity deleteRefund(@PathVariable Long id) { - try { - refundService.deleteRefund(id); - Map response = new HashMap<>(); - response.put("message", "Refund deleted successfully"); - return ResponseEntity.ok(response); - } catch (RuntimeException e) { - Map error = new HashMap<>(); - error.put("message", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); - } + public ResponseEntity deleteRefund(@PathVariable Long id) { + refundService.deleteRefund(id); + return ResponseEntity.noContent().build(); } } diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/MyPetRequest.java b/backend/src/main/java/com/petshop/backend/dto/pet/MyPetRequest.java new file mode 100644 index 00000000..17d08e2f --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/pet/MyPetRequest.java @@ -0,0 +1,42 @@ +package com.petshop.backend.dto.pet; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class MyPetRequest { + + @NotBlank(message = "Pet name is required") + @Size(max = 50, message = "Pet name must not exceed 50 characters") + private String petName; + + @NotBlank(message = "Species is required") + @Size(max = 50, message = "Species must not exceed 50 characters") + private String species; + + @Size(max = 50, message = "Breed must not exceed 50 characters") + private String breed; + + public String getPetName() { + return petName; + } + + public void setPetName(String petName) { + this.petName = petName; + } + + public String getSpecies() { + return species; + } + + public void setSpecies(String species) { + this.species = species; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } +} diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/MyPetResponse.java b/backend/src/main/java/com/petshop/backend/dto/pet/MyPetResponse.java new file mode 100644 index 00000000..7063b2f8 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/pet/MyPetResponse.java @@ -0,0 +1,61 @@ +package com.petshop.backend.dto.pet; + +public class MyPetResponse { + + private Long customerPetId; + private String petName; + private String species; + private String breed; + private String imageUrl; + + public MyPetResponse() { + } + + public MyPetResponse(Long customerPetId, String petName, String species, String breed, String imageUrl) { + this.customerPetId = customerPetId; + this.petName = petName; + this.species = species; + this.breed = breed; + this.imageUrl = imageUrl; + } + + public Long getCustomerPetId() { + return customerPetId; + } + + public void setCustomerPetId(Long customerPetId) { + this.customerPetId = customerPetId; + } + + public String getPetName() { + return petName; + } + + public void setPetName(String petName) { + this.petName = petName; + } + + public String getSpecies() { + return species; + } + + public void setSpecies(String species) { + this.species = species; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } +} diff --git a/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java b/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java index 39f4d66c..0fcb82ec 100644 --- a/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java +++ b/backend/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java @@ -1,6 +1,7 @@ package com.petshop.backend.exception; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpStatus; @@ -13,7 +14,10 @@ import java.time.LocalDateTime; @Component public class ApiErrorResponder { - private final ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build(); + private final ObjectMapper objectMapper = JsonMapper.builder() + .findAndAddModules() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); public void write(HttpServletResponse response, HttpStatus status, String message, String details, String path) throws IOException { response.setStatus(status.value()); diff --git a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index b41f8789..81163287 100644 --- a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -1,15 +1,19 @@ package com.petshop.backend.exception; +import com.petshop.backend.service.PetService; import jakarta.servlet.http.HttpServletRequest; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.core.PropertyReferenceException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.resource.NoResourceFoundException; import java.time.LocalDateTime; import java.util.HashMap; @@ -78,6 +82,26 @@ public class GlobalExceptionHandler { return buildErrorResponse(HttpStatus.valueOf(ex.getStatusCode().value()), message, ex, request); } + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFound(NoResourceFoundException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.NOT_FOUND, "Route not found", ex, request); + } + + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResponseEntity handleMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), ex, request); + } + + @ExceptionHandler(PropertyReferenceException.class) + public ResponseEntity handleBadSortProperty(PropertyReferenceException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid sort field: " + ex.getPropertyName(), ex, request); + } + + @ExceptionHandler(PetService.ForbiddenImageAccessException.class) + public ResponseEntity handleForbiddenImageAccess(PetService.ForbiddenImageAccessException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.FORBIDDEN, "Access to this pet image is not allowed", ex, request); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleGenericException(Exception ex, HttpServletRequest request) { String message = ex.getMessage() == null || ex.getMessage().isBlank() diff --git a/backend/src/main/java/com/petshop/backend/repository/PetRepository.java b/backend/src/main/java/com/petshop/backend/repository/PetRepository.java index fd55fabe..73f05460 100644 --- a/backend/src/main/java/com/petshop/backend/repository/PetRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/PetRepository.java @@ -9,11 +9,14 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository public interface PetRepository extends JpaRepository { List findAllByPetStatusIgnoreCaseOrderByPetNameAsc(String petStatus); + List findAllByOwner_IdOrderByPetNameAsc(Long ownerId); + Optional findByIdAndOwner_Id(Long id, Long ownerId); @Query("SELECT p FROM Pet p WHERE " + "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + diff --git a/backend/src/main/java/com/petshop/backend/service/PetService.java b/backend/src/main/java/com/petshop/backend/service/PetService.java index 09dd402b..bbd39085 100644 --- a/backend/src/main/java/com/petshop/backend/service/PetService.java +++ b/backend/src/main/java/com/petshop/backend/service/PetService.java @@ -1,6 +1,8 @@ package com.petshop.backend.service; import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.pet.MyPetRequest; +import com.petshop.backend.dto.pet.MyPetResponse; import com.petshop.backend.dto.pet.PetRequest; import com.petshop.backend.dto.pet.PetResponse; import com.petshop.backend.entity.Adoption; @@ -25,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.List; import java.util.Locale; @Service @@ -82,6 +85,50 @@ public class PetService { return mapToResponse(pet); } + @Transactional(readOnly = true) + public List getMyPets(Long ownerUserId) { + return petRepository.findAllByOwner_IdOrderByPetNameAsc(ownerUserId).stream() + .map(this::mapToMyPetResponse) + .toList(); + } + + @Transactional + public MyPetResponse createMyPet(Long ownerUserId, MyPetRequest request) { + User owner = userRepository.findById(ownerUserId) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + ownerUserId)); + Pet pet = new Pet(); + pet.setOwner(owner); + pet.setStore(null); + pet.setPetStatus("Owned"); + applyMyPetRequest(pet, request); + return mapToMyPetResponse(petRepository.save(pet)); + } + + @Transactional + public MyPetResponse updateMyPet(Long ownerUserId, Long petId, MyPetRequest request) { + Pet pet = findOwnedPet(ownerUserId, petId); + pet.setPetStatus("Owned"); + pet.setStore(null); + applyMyPetRequest(pet, request); + return mapToMyPetResponse(petRepository.save(pet)); + } + + @Transactional + public void deleteMyPet(Long ownerUserId, Long petId) { + Pet pet = findOwnedPet(ownerUserId, petId); + deleteStoredImageIfPresent(pet.getImageUrl()); + petRepository.delete(pet); + } + + @Transactional + public MyPetResponse uploadMyPetImage(Long ownerUserId, Long petId, MultipartFile file) throws IOException { + validateImageFile(file); + Pet pet = findOwnedPet(ownerUserId, petId); + deleteStoredImageIfPresent(pet.getImageUrl()); + pet.setImageUrl(catalogImageStorageService.storePetImage(file)); + return mapToMyPetResponse(petRepository.save(pet)); + } + @Transactional public PetResponse createPet(PetRequest request) { Pet pet = new Pet(); @@ -225,6 +272,11 @@ public class PetService { } } + private Pet findOwnedPet(Long ownerUserId, Long petId) { + return petRepository.findByIdAndOwner_Id(petId, ownerUserId) + .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + petId)); + } + private void deleteStoredImageIfPresent(String storedImagePath) { if (storedImagePath == null || storedImagePath.isBlank()) { return; @@ -276,6 +328,32 @@ public class PetService { ); } + private MyPetResponse mapToMyPetResponse(Pet pet) { + return new MyPetResponse( + pet.getPetId(), + pet.getPetName(), + pet.getPetSpecies(), + pet.getPetBreed(), + pet.getImageUrl() != null && !pet.getImageUrl().isBlank() ? "/api/v1/pets/" + pet.getPetId() + "/image" : null + ); + } + + private void applyMyPetRequest(Pet pet, MyPetRequest request) { + pet.setPetName(request.getPetName().trim()); + pet.setPetSpecies(request.getSpecies().trim()); + pet.setPetBreed(normalizeOptional(request.getBreed())); + pet.setPetAge(null); + pet.setPetPrice(null); + } + + private String normalizeOptional(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } + private void applyOwnerAndStore(Pet pet, PetRequest request) { if ("owned".equalsIgnoreCase(request.getPetStatus())) { if (request.getCustomerId() != null) { diff --git a/backend/src/main/java/com/petshop/backend/service/RefundService.java b/backend/src/main/java/com/petshop/backend/service/RefundService.java index 49cc2f69..90bf397b 100644 --- a/backend/src/main/java/com/petshop/backend/service/RefundService.java +++ b/backend/src/main/java/com/petshop/backend/service/RefundService.java @@ -8,6 +8,8 @@ import com.petshop.backend.entity.Product; import com.petshop.backend.entity.Refund; import com.petshop.backend.entity.RefundItem; import com.petshop.backend.entity.Sale; +import com.petshop.backend.exception.BusinessException; +import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.ProductRepository; import com.petshop.backend.repository.RefundRepository; import com.petshop.backend.repository.SaleRepository; @@ -40,14 +42,14 @@ public class RefundService { @Transactional public RefundResponse createRefund(RefundRequest request, Long customerId) { Sale sale = saleRepository.findById(request.getSaleId()) - .orElseThrow(() -> new RuntimeException("Sale not found")); + .orElseThrow(() -> new BusinessException("Sale not found")); if (sale.getCustomer() == null) { - throw new RuntimeException("Sale has no associated customer"); + throw new BusinessException("Sale has no associated customer"); } if (customerId != null && !sale.getCustomer().getId().equals(customerId)) { - throw new RuntimeException("You can only create refunds for your own purchases"); + throw new BusinessException("You can only create refunds for your own purchases"); } Refund refund = new Refund(); @@ -59,13 +61,13 @@ public class RefundService { BigDecimal totalAmount = BigDecimal.ZERO; for (var itemRequest : request.getItems()) { Product product = productRepository.findById(itemRequest.getProdId()) - .orElseThrow(() -> new RuntimeException("Product not found: " + itemRequest.getProdId())); - + .orElseThrow(() -> new BusinessException("Product not found: " + itemRequest.getProdId())); + BigDecimal unitPrice = sale.getItems().stream() .filter(item -> item.getProduct().getProdId().equals(itemRequest.getProdId())) .findFirst() .map(item -> item.getUnitPrice()) - .orElseThrow(() -> new RuntimeException("Product " + itemRequest.getProdId() + " was not in original sale")); + .orElseThrow(() -> new BusinessException("Product " + itemRequest.getProdId() + " was not in original sale")); RefundItem refundItem = new RefundItem(); refundItem.setProduct(product); @@ -84,10 +86,10 @@ public class RefundService { @Transactional(readOnly = true) public RefundResponse getRefundById(Long id, Long customerId) { Refund refund = refundRepository.findById(id) - .orElseThrow(() -> new RuntimeException("Refund not found")); + .orElseThrow(() -> new ResourceNotFoundException("Refund not found")); if (customerId != null && !refund.getCustomerId().equals(customerId)) { - throw new RuntimeException("You can only view your own refunds"); + throw new ResourceNotFoundException("You can only view your own refunds"); } return toResponse(refund); @@ -111,18 +113,18 @@ public class RefundService { @Transactional public RefundResponse updateRefundStatus(Long id, String status) { Refund refund = refundRepository.findById(id) - .orElseThrow(() -> new RuntimeException("Refund not found")); + .orElseThrow(() -> new ResourceNotFoundException("Refund not found")); Refund.RefundStatus newStatus; try { newStatus = Refund.RefundStatus.valueOf(status.toUpperCase()); } catch (IllegalArgumentException e) { - throw new RuntimeException("Invalid status: " + status); + throw new BusinessException("Invalid status: " + status); } if (refund.getStatus() == Refund.RefundStatus.PENDING && newStatus == Refund.RefundStatus.APPROVED) { Sale originalSale = saleRepository.findById(refund.getSaleId()) - .orElseThrow(() -> new RuntimeException("Original sale not found")); + .orElseThrow(() -> new ResourceNotFoundException("Original sale not found")); SaleRequest saleRequest = new SaleRequest(); saleRequest.setStoreId(originalSale.getStore().getStoreId()); @@ -150,7 +152,7 @@ public class RefundService { @Transactional public void deleteRefund(Long id) { if (!refundRepository.existsById(id)) { - throw new RuntimeException("Refund not found"); + throw new ResourceNotFoundException("Refund not found"); } refundRepository.deleteById(id); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java index 830488f1..af596ade 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java @@ -6,6 +6,7 @@ public class AdoptionRequest { private Long petId; private Long customerId; private Long employeeId; + private Long sourceStoreId; private LocalDate adoptionDate; private String adoptionStatus; @@ -36,6 +37,14 @@ public class AdoptionRequest { this.employeeId = employeeId; } + public Long getSourceStoreId() { + return sourceStoreId; + } + + public void setSourceStoreId(Long sourceStoreId) { + this.sourceStoreId = sourceStoreId; + } + public LocalDate getAdoptionDate() { return adoptionDate; } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentRequest.java index c87b2e87..e8166411 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentRequest.java @@ -2,11 +2,8 @@ package org.example.petshopdesktop.api.dto.appointment; import java.time.LocalDate; import java.time.LocalTime; -import java.util.List; public class AppointmentRequest { - private List petIds; - private List customerPetIds; private Long customerId; private Long storeId; private Long serviceId; @@ -14,26 +11,11 @@ public class AppointmentRequest { private LocalDate appointmentDate; private LocalTime appointmentTime; private String appointmentStatus; + private Long petId; public AppointmentRequest() { } - public List getPetIds() { - return petIds; - } - - public void setPetIds(List petIds) { - this.petIds = petIds; - } - - public List getCustomerPetIds() { - return customerPetIds; - } - - public void setCustomerPetIds(List customerPetIds) { - this.customerPetIds = customerPetIds; - } - public Long getCustomerId() { return customerId; } @@ -89,4 +71,12 @@ public class AppointmentRequest { public void setAppointmentStatus(String appointmentStatus) { this.appointmentStatus = appointmentStatus; } + + public Long getPetId() { + return petId; + } + + public void setPetId(Long petId) { + this.petId = petId; + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentResponse.java index d1768a94..dd74554b 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/appointment/AppointmentResponse.java @@ -10,16 +10,14 @@ public class AppointmentResponse { private Long storeId; private String storeName; private Long serviceId; - private java.util.List petNames; - private java.util.List petIds; - private java.util.List customerPetNames; - private java.util.List customerPetIds; private String serviceName; private Long employeeId; private String employeeName; private LocalDate appointmentDate; private LocalTime appointmentTime; private String appointmentStatus; + private String petName; + private Long petId; public AppointmentResponse() { } @@ -72,36 +70,20 @@ public class AppointmentResponse { this.serviceId = serviceId; } - public java.util.List getPetNames() { - return petNames; + public String getPetName() { + return petName; } - public void setPetNames(java.util.List petNames) { - this.petNames = petNames; + public void setPetName(String petName) { + this.petName = petName; } - public java.util.List getPetIds() { - return petIds; + public Long getPetId() { + return petId; } - public void setPetIds(java.util.List petIds) { - this.petIds = petIds; - } - - public java.util.List getCustomerPetNames() { - return customerPetNames; - } - - public void setCustomerPetNames(java.util.List customerPetNames) { - this.customerPetNames = customerPetNames; - } - - public java.util.List getCustomerPetIds() { - return customerPetIds; - } - - public void setCustomerPetIds(java.util.List customerPetIds) { - this.customerPetIds = customerPetIds; + public void setPetId(Long petId) { + this.petId = petId; } public String getServiceName() { diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageRequest.java index a5c17ca4..cd8efedf 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageRequest.java @@ -2,6 +2,10 @@ package org.example.petshopdesktop.api.dto.chat; public class MessageRequest { private String content; + private String attachmentUrl; + private String attachmentName; + private String attachmentMimeType; + private Long attachmentSizeBytes; public MessageRequest() { } @@ -17,4 +21,36 @@ public class MessageRequest { public void setContent(String content) { this.content = content; } + + public String getAttachmentUrl() { + return attachmentUrl; + } + + public void setAttachmentUrl(String attachmentUrl) { + this.attachmentUrl = attachmentUrl; + } + + public String getAttachmentName() { + return attachmentName; + } + + public void setAttachmentName(String attachmentName) { + this.attachmentName = attachmentName; + } + + public String getAttachmentMimeType() { + return attachmentMimeType; + } + + public void setAttachmentMimeType(String attachmentMimeType) { + this.attachmentMimeType = attachmentMimeType; + } + + public Long getAttachmentSizeBytes() { + return attachmentSizeBytes; + } + + public void setAttachmentSizeBytes(Long attachmentSizeBytes) { + this.attachmentSizeBytes = attachmentSizeBytes; + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java index f81db82d..096a6e58 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/chat/MessageResponse.java @@ -9,6 +9,10 @@ public class MessageResponse { private String content; private LocalDateTime timestamp; private Boolean isRead; + private String attachmentUrl; + private String attachmentName; + private String attachmentMimeType; + private Long attachmentSizeBytes; public MessageResponse() { } @@ -60,4 +64,36 @@ public class MessageResponse { public void setIsRead(Boolean isRead) { this.isRead = isRead; } + + public String getAttachmentUrl() { + return attachmentUrl; + } + + public void setAttachmentUrl(String attachmentUrl) { + this.attachmentUrl = attachmentUrl; + } + + public String getAttachmentName() { + return attachmentName; + } + + public void setAttachmentName(String attachmentName) { + this.attachmentName = attachmentName; + } + + public String getAttachmentMimeType() { + return attachmentMimeType; + } + + public void setAttachmentMimeType(String attachmentMimeType) { + this.attachmentMimeType = attachmentMimeType; + } + + public Long getAttachmentSizeBytes() { + return attachmentSizeBytes; + } + + public void setAttachmentSizeBytes(Long attachmentSizeBytes) { + this.attachmentSizeBytes = attachmentSizeBytes; + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java index f047f641..dd2a7271 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java @@ -5,9 +5,12 @@ public class EmployeeRequest { private String password; private String firstName; private String lastName; + private String fullName; private String email; private String phone; private String role; + private String staffRole; + private Long primaryStoreId; private Boolean active; public String getUsername() { return username; } @@ -18,12 +21,18 @@ public class EmployeeRequest { public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } + public String getFullName() { return fullName; } + public void setFullName(String fullName) { this.fullName = fullName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } + public String getStaffRole() { return staffRole; } + public void setStaffRole(String staffRole) { this.staffRole = staffRole; } + public Long getPrimaryStoreId() { return primaryStoreId; } + public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; } public Boolean getActive() { return active; } public void setActive(Boolean active) { this.active = active; } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java index 030488c1..f9ce7f96 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java @@ -3,6 +3,7 @@ package org.example.petshopdesktop.api.dto.employee; import java.time.LocalDateTime; public class EmployeeResponse { + private Long id; private Long employeeId; private Long userId; private String username; @@ -12,13 +13,17 @@ public class EmployeeResponse { private String email; private String phone; private String role; + private String staffRole; + private Long primaryStoreId; private Boolean active; private LocalDateTime createdAt; private LocalDateTime updatedAt; - public Long getEmployeeId() { return employeeId; } + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public Long getEmployeeId() { return employeeId != null ? employeeId : id; } public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; } - public Long getUserId() { return userId; } + public Long getUserId() { return userId != null ? userId : id; } public void setUserId(Long userId) { this.userId = userId; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @@ -34,6 +39,10 @@ public class EmployeeResponse { public void setPhone(String phone) { this.phone = phone; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } + public String getStaffRole() { return staffRole; } + public void setStaffRole(String staffRole) { this.staffRole = staffRole; } + public Long getPrimaryStoreId() { return primaryStoreId; } + public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; } public Boolean getActive() { return active; } public void setActive(Boolean active) { this.active = active; } public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryRequest.java index 41196003..935a713e 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryRequest.java @@ -3,6 +3,7 @@ package org.example.petshopdesktop.api.dto.inventory; public class InventoryRequest { private Long prodId; private Integer quantity; + private Long storeId; public InventoryRequest() { } @@ -22,4 +23,12 @@ public class InventoryRequest { public void setQuantity(Integer quantity) { this.quantity = quantity; } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryResponse.java index 1767f751..176228b9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/inventory/InventoryResponse.java @@ -7,6 +7,8 @@ public class InventoryResponse { private Long prodId; private String productName; private String categoryName; + private Long storeId; + private String storeName; private Integer quantity; private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -46,6 +48,22 @@ public class InventoryResponse { this.categoryName = categoryName; } + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + public Integer getQuantity() { return quantity; } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java index d183918a..039e4b4f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java @@ -231,18 +231,12 @@ public class AppointmentController { } private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) { - Long petId = response.getCustomerPetIds() != null && !response.getCustomerPetIds().isEmpty() - ? response.getCustomerPetIds().get(0) - : response.getPetIds() != null && !response.getPetIds().isEmpty() ? response.getPetIds().get(0) : null; - String petName = response.getCustomerPetNames() != null && !response.getCustomerPetNames().isEmpty() - ? String.join(", ", response.getCustomerPetNames()) - : String.join(", ", response.getPetNames()); return new AppointmentDTO( response.getAppointmentId().intValue(), response.getCustomerId() != null ? response.getCustomerId().intValue() : 0, response.getCustomerName(), - petId != null ? petId.intValue() : 0, - petName, + response.getPetId() != null ? response.getPetId().intValue() : 0, + response.getPetName(), response.getServiceId() != null ? response.getServiceId().intValue() : 0, response.getServiceName(), response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0, diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java index bc343639..f9dc4940 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java @@ -123,20 +123,28 @@ public class ChatController { @FXML void btnSendClicked() { - if (selectedConversation == null) { - lblChatStatus.setText("Select a conversation"); - return; - } + try { + if (selectedConversation == null) { + lblChatStatus.setText("Select a conversation"); + return; + } - String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim(); - if (content.isEmpty()) { - return; - } + String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim(); + if (content.isEmpty()) { + return; + } - txtMessage.clear(); - boolean sent = realtimeClient.sendMessage(selectedConversation.getId(), content); - if (!sent) { - sendMessageFallback(selectedConversation.getId(), content); + txtMessage.clear(); + boolean sent = realtimeClient.sendMessage(selectedConversation.getId(), content); + if (!sent) { + sendMessageFallback(selectedConversation.getId(), content); + } + } catch (Exception e) { + ActivityLogger.getInstance().logException( + "ChatController.btnSendClicked", + e, + "Sending chat message"); + lblChatStatus.setText("Chat send failed"); } } @@ -223,16 +231,30 @@ public class ChatController { private void renderMessages(List messages) { vbMessages.getChildren().clear(); for (MessageResponse message : messages) { - vbMessages.getChildren().add(createMessageBubble(message)); + try { + vbMessages.getChildren().add(createMessageBubble(message)); + } catch (Exception e) { + ActivityLogger.getInstance().logException( + "ChatController.renderMessages", + e, + "Rendering chat message"); + } } scrollMessagesToBottom(); } private void appendMessageIfSelected(MessageResponse message) { - upsertConversationForMessage(message); - if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) { - vbMessages.getChildren().add(createMessageBubble(message)); - scrollMessagesToBottom(); + try { + upsertConversationForMessage(message); + if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) { + vbMessages.getChildren().add(createMessageBubble(message)); + scrollMessagesToBottom(); + } + } catch (Exception e) { + ActivityLogger.getInstance().logException( + "ChatController.appendMessageIfSelected", + e, + "Appending chat message"); } } @@ -284,15 +306,34 @@ public class ChatController { Label author = new Label(resolveAuthorLabel(message)); author.setStyle("-fx-font-weight: bold; -fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";"); - Label content = new Label(message.getContent()); - content.setWrapText(true); - content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";"); - String timestampText = message.getTimestamp() == null ? "" : TIME_FORMATTER.format(message.getTimestamp()); Label timestamp = new Label(timestampText); timestamp.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#94a3b8") + "; -fx-font-size: 11px;"); - VBox bubble = new VBox(4, author, content, timestamp); + VBox bubble = new VBox(4, author); + String contentText = message.getContent() == null ? "" : message.getContent(); + if (!contentText.isBlank()) { + Label content = new Label(contentText); + content.setWrapText(true); + content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";"); + bubble.getChildren().add(content); + } + + if (message.getAttachmentUrl() != null && !message.getAttachmentUrl().isBlank()) { + String attachmentLabel = message.getAttachmentName(); + if (attachmentLabel == null || attachmentLabel.isBlank()) { + attachmentLabel = "Attachment"; + } + if (message.getAttachmentSizeBytes() != null && message.getAttachmentSizeBytes() > 0) { + attachmentLabel = attachmentLabel + " (" + formatSize(message.getAttachmentSizeBytes()) + ")"; + } + Label attachment = new Label(attachmentLabel); + attachment.setWrapText(true); + attachment.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#0f766e") + "; -fx-underline: true;"); + bubble.getChildren().add(attachment); + } + + bubble.getChildren().add(timestamp); bubble.setMaxWidth(420); bubble.setStyle(mine ? "-fx-background-color: #0f766e; -fx-background-radius: 14; -fx-padding: 12;" @@ -347,4 +388,17 @@ public class ChatController { private void scrollMessagesToBottom() { Platform.runLater(() -> spMessages.setVvalue(1.0)); } + private String formatSize(Long bytes) { + if (bytes == null || bytes <= 0) { + return ""; + } + double size = bytes; + String[] units = {"B", "KB", "MB", "GB"}; + int unitIndex = 0; + while (size >= 1024 && unitIndex < units.length - 1) { + size = size / 1024; + unitIndex++; + } + return unitIndex == 0 ? String.format("%.0f %s", size, units[unitIndex]) : String.format("%.1f %s", size, units[unitIndex]); + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java index f5f83461..06b2afa9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java @@ -235,8 +235,8 @@ public class InventoryController { response.getProdId() != null ? response.getProdId().intValue() : 0, response.getProductName(), response.getCategoryName() != null ? response.getCategoryName() : "", - 0, - "N/A", + response.getStoreId() != null ? response.getStoreId().intValue() : 0, + response.getStoreName() != null ? response.getStoreName() : "", response.getQuantity() != null ? response.getQuantity() : 0, 0 ); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java index 603b85fa..bec351a4 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java @@ -186,10 +186,14 @@ public class StaffAccountsController { long userId = employee.getUserId() != null ? employee.getUserId() : 0L; long employeeId = employee.getEmployeeId() != null ? employee.getEmployeeId() : 0L; String username = employee.getUsername(); - String fullName = employee.getFullName() != null ? employee.getFullName() : ""; - String[] names = splitFullName(fullName); - String firstName = names[0]; - String lastName = names[1]; + String firstName = employee.getFirstName() != null ? employee.getFirstName() : ""; + String lastName = employee.getLastName() != null ? employee.getLastName() : ""; + if (firstName.isBlank() && lastName.isBlank()) { + String fullName = employee.getFullName() != null ? employee.getFullName() : ""; + String[] names = splitFullName(fullName); + firstName = names[0]; + lastName = names[1]; + } String email = employee.getEmail() != null ? employee.getEmail() : ""; String phone = employee.getPhone() != null ? employee.getPhone() : ""; String role = employee.getRole() != null ? employee.getRole() : "STAFF"; diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java index 67e1073f..0240c128 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java @@ -190,10 +190,16 @@ public class AdoptionDialogController { if (errorMsg.isEmpty()) { try { + Long storeId = UserSession.getInstance().getStoreId(); + if (storeId == null || storeId <= 0) { + throw new IllegalStateException("Store is not set for this account"); + } + AdoptionRequest request = new AdoptionRequest(); request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId()); request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId()); request.setEmployeeId(cbEmployee.getSelectionModel().getSelectedItem().getId()); + request.setSourceStoreId(storeId); request.setAdoptionDate(dpAdoptionDate.getValue()); request.setAdoptionStatus(cbAdoptionStatus.getValue()); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java index d21ad756..139255c6 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java @@ -21,7 +21,6 @@ import org.example.petshopdesktop.util.ActivityLogger; import java.time.LocalTime; import java.time.LocalDate; import java.util.List; -import java.util.Collections; import java.util.Objects; public class AppointmentDialogController { @@ -215,7 +214,7 @@ public class AppointmentDialogController { } AppointmentRequest request = new AppointmentRequest(); - request.setCustomerPetIds(Collections.singletonList(cbPet.getValue().getId())); + request.setPetId(cbPet.getValue().getId()); request.setCustomerId(cbCustomer.getValue().getId()); request.setStoreId(storeId); request.setServiceId(cbService.getValue().getId()); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java index 0b410186..499b5ae8 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java @@ -20,6 +20,7 @@ import org.example.petshopdesktop.api.dto.inventory.InventoryResponse; import org.example.petshopdesktop.api.dto.product.ProductResponse; import org.example.petshopdesktop.api.endpoints.InventoryApi; import org.example.petshopdesktop.api.endpoints.ProductApi; +import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.models.Product; import org.example.petshopdesktop.util.ActivityLogger; @@ -127,6 +128,10 @@ public class InventoryDialogController { try { InventoryRequest request = new InventoryRequest(); Product selectedProduct = cbProduct.getSelectionModel().getSelectedItem(); + Long storeId = UserSession.getInstance().getStoreId(); + if (storeId == null || storeId <= 0) { + throw new IllegalStateException("Store is not set for this account"); + } request.setProdId((long) selectedProduct.getProdId()); int quantity; try { @@ -135,6 +140,7 @@ public class InventoryDialogController { throw new IllegalArgumentException("Invalid quantity format"); } request.setQuantity(quantity); + request.setStoreId(storeId); if (mode.equals("Add")) { InventoryApi.getInstance().createInventory(request); @@ -206,4 +212,4 @@ public class InventoryDialogController { lblMode.setText(mode + " Inventory"); lblInventoryId.setVisible(mode.equals("Edit")); } -} \ No newline at end of file +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java index 5ed6316e..5b8b4d6a 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java @@ -11,6 +11,7 @@ import javafx.stage.Stage; import org.example.petshopdesktop.Validator; import org.example.petshopdesktop.api.dto.employee.EmployeeRequest; import org.example.petshopdesktop.api.endpoints.EmployeeApi; +import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.models.StaffAccount; import org.example.petshopdesktop.util.ActivityLogger; @@ -104,14 +105,18 @@ public class StaffEditDialogController { new Thread(() -> { try { + Long storeId = UserSession.getInstance().getStoreId(); EmployeeRequest request = new EmployeeRequest(); request.setUsername(username); request.setPassword(password.isEmpty() ? null : password); request.setFirstName(firstName); request.setLastName(lastName); + request.setFullName(firstName + " " + lastName); request.setEmail(email); request.setPhone(phone); request.setRole(staffAccount.getRole()); + request.setStaffRole("Staff"); + request.setPrimaryStoreId(storeId); request.setActive(staffAccount.isActive()); EmployeeApi.getInstance().updateEmployee(staffAccount.getEmployeeId(), request); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java index 8d121dde..5be1a27e 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java @@ -11,6 +11,7 @@ import javafx.scene.control.TextField; import javafx.stage.Stage; import org.example.petshopdesktop.api.dto.employee.EmployeeRequest; import org.example.petshopdesktop.api.endpoints.EmployeeApi; +import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.Validator; import org.example.petshopdesktop.util.ActivityLogger; @@ -89,14 +90,18 @@ public class StaffRegisterDialogController { new Thread(() -> { try { + Long storeId = UserSession.getInstance().getStoreId(); EmployeeRequest request = new EmployeeRequest(); request.setUsername(username); request.setPassword(password); request.setFirstName(firstName); request.setLastName(lastName); + request.setFullName(firstName + " " + lastName); request.setEmail(email); request.setPhone(phone); request.setRole("STAFF"); + request.setStaffRole("Staff"); + request.setPrimaryStoreId(storeId); request.setActive(true); EmployeeApi.getInstance().createEmployee(request); diff --git a/web/package-lock.json b/web/package-lock.json index 2ccc06fb..d85b7abd 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,7 +8,7 @@ "name": "threaded-pets", "version": "0.1.0", "dependencies": { - "next": "16.1.6", + "next": "^16.2.2", "react": "19.2.3", "react-dom": "19.2.3" }, @@ -1032,9 +1032,9 @@ } }, "node_modules/@next/env": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", - "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.2.tgz", + "integrity": "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1048,9 +1048,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", - "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.2.tgz", + "integrity": "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg==", "cpu": [ "arm64" ], @@ -1064,9 +1064,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", - "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.2.tgz", + "integrity": "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw==", "cpu": [ "x64" ], @@ -1080,9 +1080,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", - "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.2.tgz", + "integrity": "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==", "cpu": [ "arm64" ], @@ -1096,9 +1096,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", - "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.2.tgz", + "integrity": "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==", "cpu": [ "arm64" ], @@ -1112,9 +1112,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", - "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.2.tgz", + "integrity": "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==", "cpu": [ "x64" ], @@ -1128,9 +1128,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", - "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.2.tgz", + "integrity": "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==", "cpu": [ "x64" ], @@ -1144,9 +1144,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", - "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.2.tgz", + "integrity": "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==", "cpu": [ "arm64" ], @@ -1160,9 +1160,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", - "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.2.tgz", + "integrity": "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA==", "cpu": [ "x64" ], @@ -1741,9 +1741,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2422,9 +2422,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -3568,9 +3568,9 @@ } }, "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -4954,14 +4954,14 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.1.6", - "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", - "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.2.tgz", + "integrity": "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==", "license": "MIT", "dependencies": { - "@next/env": "16.1.6", + "@next/env": "16.2.2", "@swc/helpers": "0.5.15", - "baseline-browser-mapping": "^2.8.3", + "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -4973,15 +4973,15 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.1.6", - "@next/swc-darwin-x64": "16.1.6", - "@next/swc-linux-arm64-gnu": "16.1.6", - "@next/swc-linux-arm64-musl": "16.1.6", - "@next/swc-linux-x64-gnu": "16.1.6", - "@next/swc-linux-x64-musl": "16.1.6", - "@next/swc-win32-arm64-msvc": "16.1.6", - "@next/swc-win32-x64-msvc": "16.1.6", - "sharp": "^0.34.4" + "@next/swc-darwin-arm64": "16.2.2", + "@next/swc-darwin-x64": "16.2.2", + "@next/swc-linux-arm64-gnu": "16.2.2", + "@next/swc-linux-arm64-musl": "16.2.2", + "@next/swc-linux-x64-gnu": "16.2.2", + "@next/swc-linux-x64-musl": "16.2.2", + "@next/swc-win32-arm64-msvc": "16.2.2", + "@next/swc-win32-x64-msvc": "16.2.2", + "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -5298,9 +5298,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -6099,9 +6099,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { diff --git a/web/package.json b/web/package.json index 20ad5734..5cb43e9f 100644 --- a/web/package.json +++ b/web/package.json @@ -9,7 +9,7 @@ "lint": "eslint" }, "dependencies": { - "next": "16.1.6", + "next": "^16.2.2", "react": "19.2.3", "react-dom": "19.2.3" },