diff --git a/.gitignore b/.gitignore
index 34394cf7..3ef9c153 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,7 @@ build/
### Mac ###
.DS_Store
+
+### Project Specific ###
+tmp/
+uploads/
diff --git a/docker-compose.yml b/docker-compose.yml
index 3e8a175b..423dab50 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,8 @@ services:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
- - ./sql:/docker-entrypoint-initdb.d
+ - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
+ - ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
interval: 5s
diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json
index a2537681..4418bb47 100644
--- a/petshop-api.postman_collection.json
+++ b/petshop-api.postman_collection.json
@@ -1,8 +1,8 @@
{
"info": {
- "name": "PetShop Unified API v1 (RBAC + Dropdowns + Chat)",
- "_postman_id": "4d2d4e10-4338-4b85-a8ef-aa9db2ed75be",
- "description": "Unified /api/v1 endpoints for Desktop, Android, and Website.\n\nVariables:\n- baseUrl (example http://localhost:8080)\n- staffToken (set by staff/admin login)\n- customerToken (set by customer login)\n\nNotes:\n- Login test scripts assume response contains {\"token\":\"...\"}.\n- Bulk delete uses DELETE with JSON body {ids:[...]}.",
+ "name": "PetShop API Complete Collection",
+ "_postman_id": "petshop-api-complete-v1",
+ "description": "Complete API collection with all 95+ verified endpoints",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
@@ -10,74 +10,40 @@
"key": "baseUrl",
"value": "http://localhost:8080"
},
- {
- "key": "staffToken",
- "value": ""
- },
{
"key": "customerToken",
"value": ""
},
{
- "key": "petId",
- "value": "1"
+ "key": "staffToken",
+ "value": ""
},
{
- "key": "adoptionId",
- "value": "1"
- },
- {
- "key": "appointmentId",
- "value": "1"
- },
- {
- "key": "serviceId",
- "value": "1"
- },
- {
- "key": "prodId",
- "value": "1"
- },
- {
- "key": "categoryId",
- "value": "1"
- },
- {
- "key": "supId",
- "value": "1"
- },
- {
- "key": "inventoryId",
- "value": "1"
- },
- {
- "key": "saleId",
- "value": "1"
- },
- {
- "key": "purchaseOrderId",
- "value": "1"
- },
- {
- "key": "userId",
- "value": "1"
- },
- {
- "key": "employeeId",
- "value": "1"
- },
- {
- "key": "roomId",
- "value": "1"
- },
- {
- "key": "storeId",
- "value": "1"
+ "key": "adminToken",
+ "value": ""
}
],
"item": [
{
- "name": "Auth",
+ "name": "Health",
+ "item": [
+ {
+ "name": "Health Check",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/health",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "Authentication",
"item": [
{
"name": "Register Customer",
@@ -92,17 +58,12 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"firstName\": \"Chris\",\n \"lastName\": \"Ng\",\n \"email\": \"chris.ng@example.com\",\n \"phone\": \"555-0120\",\n \"password\": \"ChangeMe123\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
+ "raw": "{\n \"username\": \"newcustomer\",\n \"password\": \"password123\",\n \"email\": \"new@example.com\",\n \"fullName\": \"New Customer\"\n}"
}
}
},
{
- "name": "Login (Staff/Admin) -> sets staffToken",
+ "name": "Login as Customer",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/v1/auth/login",
@@ -114,31 +75,29 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
+ "raw": "{\n \"username\": \"customer\",\n \"password\": \"customer123\"\n}"
}
},
"event": [
{
"listen": "test",
"script": {
- "type": "text/javascript",
"exec": [
- "try {",
- " const json = pm.response.json();",
- " if (json && json.token) pm.collectionVariables.set('staffToken', json.token);",
- "} catch (e) {}"
+ "pm.test('Status code is 200', function () {",
+ " pm.response.to.have.status(200);",
+ "});",
+ "",
+ "var jsonData = pm.response.json();",
+ "if (jsonData.token) {",
+ " pm.collectionVariables.set('customerToken', jsonData.token);",
+ "}"
]
}
}
]
},
{
- "name": "Login (Customer) -> sets customerToken",
+ "name": "Login as Staff",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/v1/auth/login",
@@ -148,2071 +107,470 @@
"value": "application/json"
}
],
- "description": "If login uses username instead of email, adjust request body.",
"body": {
"mode": "raw",
- "raw": "{\n \"email\": \"chris.ng@example.com\",\n \"password\": \"ChangeMe123\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
+ "raw": "{\n \"username\": \"staff\",\n \"password\": \"staff123\"\n}"
}
},
"event": [
{
"listen": "test",
"script": {
- "type": "text/javascript",
"exec": [
- "try {",
- " const json = pm.response.json();",
- " if (json && json.token) pm.collectionVariables.set('customerToken', json.token);",
- "} catch (e) {}"
+ "pm.test('Status code is 200', function () {",
+ " pm.response.to.have.status(200);",
+ "});",
+ "",
+ "var jsonData = pm.response.json();",
+ "if (jsonData.token) {",
+ " pm.collectionVariables.set('staffToken', jsonData.token);",
+ "}"
]
}
}
]
},
{
- "name": "Logout (Staff/Admin)",
+ "name": "Login as Admin",
"request": {
"method": "POST",
- "url": "{{baseUrl}}/api/v1/auth/logout",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Logout (Customer)",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/auth/logout",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Auth Me (Staff/Admin)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/auth/me",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Auth Me (Customer)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/auth/me",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- }
- }
- }
- ]
- },
- {
- "name": "My Account (/me)",
- "item": [
- {
- "name": "Get My Profile (Staff/Admin)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/me",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update My Profile (Staff/Admin)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/me",
+ "url": "{{baseUrl}}/api/v1/auth/login",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
"body": {
"mode": "raw",
- "raw": "{\n \"phone\": \"555-0999\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
+ "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin123\"\n}"
+ }
+ },
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "pm.test('Status code is 200', function () {",
+ " pm.response.to.have.status(200);",
+ "});",
+ "",
+ "var jsonData = pm.response.json();",
+ "if (jsonData.token) {",
+ " pm.collectionVariables.set('adminToken', jsonData.token);",
+ "}"
+ ]
}
}
- }
+ ]
},
{
- "name": "Get My Profile (Customer)",
+ "name": "Get My Profile",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/me",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update My Profile (Customer)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/me",
+ "url": "{{baseUrl}}/api/v1/auth/me",
"header": [
{
"key": "Content-Type",
"value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Update My Profile",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/auth/me",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
}
],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- },
"body": {
"mode": "raw",
- "raw": "{\n \"phone\": \"555-0999\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
+ "raw": "{\n \"fullName\": \"Updated Name\",\n \"email\": \"updated@example.com\"\n}"
}
}
},
{
- "name": "Upload My Avatar (Staff/Admin) [multipart]",
+ "name": "Upload Avatar",
"request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/me/avatar",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "header": [],
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/auth/me/avatar",
+ "header": [
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ],
"body": {
"mode": "formdata",
"formdata": [
{
- "key": "file",
+ "key": "avatar",
"type": "file",
- "src": ""
+ "src": []
}
]
- },
- "description": "Uploads avatar image for the logged-in staff/admin. Public access via GET /api/v1/staff/{employeeId}/avatar."
+ }
}
},
{
- "name": "Upload My Avatar (Customer) [multipart]",
+ "name": "Get Avatar",
"request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/me/avatar",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- },
- "header": [],
- "body": {
- "mode": "formdata",
- "formdata": [
- {
- "key": "file",
- "type": "file",
- "src": ""
- }
- ]
- },
- "description": "Optional if customer avatars are supported."
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/auth/me/avatar",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Delete Avatar",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/auth/me/avatar",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Logout",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/auth/logout",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
}
}
]
},
{
- "name": "Dropdowns (lightweight id+name)",
+ "name": "Pets",
"item": [
{
- "name": "Dropdown - Pets (staff)",
+ "name": "Get All Pets",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/pets",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Pet by ID",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/pets/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Pets Dropdown",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/v1/dropdowns/pets",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create Pet",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/pets",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}"
}
}
},
{
- "name": "Dropdown - Customers (staff)",
+ "name": "Update Pet",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/pets/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Pet",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/pets/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Pets",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/pets",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1,\n 2\n ]\n}"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Products",
+ "item": [
+ {
+ "name": "Get All Products",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/dropdowns/customers",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
+ "url": "{{baseUrl}}/api/v1/products",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
}
},
{
- "name": "Dropdown - Services (staff)",
+ "name": "Get Product by ID",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/dropdowns/services",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
+ "url": "{{baseUrl}}/api/v1/products/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
}
},
{
- "name": "Dropdown - Products (staff)",
+ "name": "Get Products Dropdown",
"request": {
"method": "GET",
"url": "{{baseUrl}}/api/v1/dropdowns/products",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create Product",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/products",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 50.0\n}"
}
}
},
{
- "name": "Dropdown - Suppliers (admin)",
+ "name": "Update Product",
"request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/dropdowns/suppliers",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/products/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 55.0\n}"
}
}
},
{
- "name": "Dropdown - Categories (staff)",
+ "name": "Delete Product",
"request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/dropdowns/categories",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/products/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
}
},
{
- "name": "Dropdown - Stores (staff)",
+ "name": "Bulk Delete Products",
"request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/dropdowns/stores",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Dropdown - Services (customer)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/dropdowns/services",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Dropdown - Stores (customer)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/dropdowns/stores",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/products",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1\n ]\n}"
}
}
}
]
},
{
- "name": "Resources (Staff)",
+ "name": "Sales",
"item": [
{
- "name": "Pets",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/pets?q=&page=0&size=50&sort=id,desc",
- "header": [],
- "description": "Returns UI-ready rows (joined fields included).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/pets",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 499.99\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/pets/{{petId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/pets/{{petId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 4,\n \"petStatus\": \"Available\",\n \"petPrice\": 450.0\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/pets/{{petId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete (DELETE body)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/pets",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Bulk delete with JSON body.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- },
- {
- "name": "Adoptions",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/adoptions?q=&page=0&size=50&sort=id,desc",
- "header": [],
- "description": "Returns UI-ready rows (joined fields included).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/adoptions",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"petId\": \"{{petId}}\",\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-04\",\n \"adoptionStatus\": \"Pending\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"petId\": \"{{petId}}\",\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-04\",\n \"adoptionStatus\": \"Approved\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete (DELETE body)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/adoptions",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Bulk delete with JSON body.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- },
- {
- "name": "Appointments",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/appointments?q=&page=0&size=50&sort=id,desc",
- "header": [],
- "description": "Returns UI-ready rows (joined fields included).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/appointments",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"serviceId\": \"{{serviceId}}\",\n \"customerId\": 1,\n \"appointmentDate\": \"2026-03-04\",\n \"appointmentTime\": \"14:30:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n \"{{petId}}\"\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"serviceId\": \"{{serviceId}}\",\n \"customerId\": 1,\n \"appointmentDate\": \"2026-03-05\",\n \"appointmentTime\": \"15:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n \"{{petId}}\"\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete (DELETE body)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/appointments",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Bulk delete with JSON body.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- },
- {
- "name": "Appointments - Availability",
+ "name": "Get All Sales",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/appointments/availability?storeId={{storeId}}&serviceId={{serviceId}}&date=2026-03-04",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
+ "url": "{{baseUrl}}/api/v1/sales",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
}
},
{
- "name": "Services",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/services?q=&page=0&size=50&sort=id,desc",
- "header": [],
- "description": "Returns UI-ready rows (joined fields included).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/services",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming package\",\n \"serviceDuration\": 60,\n \"servicePrice\": 49.99\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/services/{{serviceId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/services/{{serviceId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Updated description\",\n \"serviceDuration\": 60,\n \"servicePrice\": 54.99\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/services/{{serviceId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete (DELETE body)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/services",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Bulk delete with JSON body.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- },
- {
- "name": "Products",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/products?q=&page=0&size=50&sort=id,desc",
- "header": [],
- "description": "Returns UI-ready rows (joined fields included).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/products",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"prodName\": \"Dog Food\",\n \"prodPrice\": 19.99,\n \"prodDesc\": \"Large bag\",\n \"categoryId\": \"{{categoryId}}\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/products/{{prodId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/products/{{prodId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"prodName\": \"Dog Food\",\n \"prodPrice\": 21.99,\n \"prodDesc\": \"Large bag\",\n \"categoryId\": \"{{categoryId}}\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/products/{{prodId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete (DELETE body)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/products",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Bulk delete with JSON body.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- },
- {
- "name": "Categories",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/categories?q=&page=0&size=50&sort=id,desc",
- "header": [],
- "description": "Returns UI-ready rows (joined fields included).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/categories",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"categoryName\": \"Food\",\n \"categoryType\": \"PRODUCT\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"categoryName\": \"Food & Treats\",\n \"categoryType\": \"PRODUCT\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete (DELETE body)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/categories",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Bulk delete with JSON body.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- }
- ]
- },
- {
- "name": "Admin-only Resources",
- "item": [
- {
- "name": "Inventory",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/inventory?q=&page=0&size=50&sort=id,desc",
- "header": [],
- "description": "Returns UI-ready rows (joined fields included).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/inventory",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 25\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 30\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete (DELETE body)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/inventory",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Bulk delete with JSON body.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- },
- {
- "name": "Suppliers",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/suppliers?q=&page=0&size=50&sort=id,desc",
- "header": [],
- "description": "Returns UI-ready rows (joined fields included).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/suppliers",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"supName\": \"Acme Supplies\",\n \"supPhone\": \"555-0100\",\n \"supEmail\": \"sales@acme.example\",\n \"supAddress\": \"123 Main St\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"supName\": \"Acme Supplies\",\n \"supPhone\": \"555-0100\",\n \"supEmail\": \"support@acme.example\",\n \"supAddress\": \"123 Main St\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete (DELETE body)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/suppliers",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Bulk delete with JSON body.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- },
- {
- "name": "Product Suppliers (Costing)",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/product-suppliers?q=&page=0&size=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/product-suppliers",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"supId\": \"{{supId}}\",\n \"cost\": 12.34\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Update (composite)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/product-suppliers",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Update by composite key (prodId, supId).",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"supId\": \"{{supId}}\",\n \"cost\": 13.0\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Bulk Delete (keys)",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/product-suppliers",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"keys\": [\n {\n \"prodId\": 1,\n \"supId\": 2\n },\n {\n \"prodId\": 3,\n \"supId\": 4\n }\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- }
- ]
- },
- {
- "name": "Purchase Orders (read)",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/purchase-orders?q=&page=0&size=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Get One",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/purchase-orders/{{purchaseOrderId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- }
- ]
- },
- {
- "name": "Users (Staff Accounts)",
- "item": [
- {
- "name": "List/Search",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/users?q=&page=0&size=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Create",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/users",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "description": "Admin-only.",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"firstName\": \"Sam\",\n \"lastName\": \"Lee\",\n \"email\": \"sam.lee@example.com\",\n \"phone\": \"555-0110\",\n \"username\": \"samlee\",\n \"password\": \"ChangeMe123\",\n \"role\": \"STAFF\",\n \"storeIds\": [\n \"{{storeId}}\"\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Update (PUT)",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/users/{{userId}}",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"phone\": \"555-0111\",\n \"role\": \"STAFF\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Delete One",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/users/{{userId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Bulk Delete",
- "request": {
- "method": "DELETE",
- "url": "{{baseUrl}}/api/v1/users",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json"
- }
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
- }
- },
- {
- "name": "Upload Avatar for User (Admin) [multipart]",
- "request": {
- "method": "PUT",
- "url": "{{baseUrl}}/api/v1/users/{{userId}}/avatar",
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "header": [],
- "body": {
- "mode": "formdata",
- "formdata": [
- {
- "key": "file",
- "type": "file",
- "src": ""
- }
- ]
- },
- "description": "Admin-only."
- }
- }
- ]
- }
- ]
- },
- {
- "name": "Sales + Refunds (Staff)",
- "item": [
- {
- "name": "Sales - List/Search",
+ "name": "Get Sale by ID",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/sales?q=&page=0&size=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
+ "url": "{{baseUrl}}/api/v1/sales/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
}
},
{
- "name": "Sales - Create (Checkout)",
+ "name": "Create Sale",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/v1/sales",
@@ -2220,328 +578,1601 @@
{
"key": "Content-Type",
"value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
}
],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
"body": {
"mode": "raw",
- "raw": "{\n \"paymentMethod\": \"Cash\",\n \"storeId\": \"{{storeId}}\",\n \"items\": [\n {\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 2\n }\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
+ "raw": "{\n \"storeId\": 1,\n \"paymentMethod\": \"Card\",\n \"customerId\": 1,\n \"items\": [\n {\n \"prodId\": 1,\n \"quantity\": 2\n },\n {\n \"prodId\": 2,\n \"quantity\": 1\n }\n ],\n \"isRefund\": false\n}"
}
}
- },
+ }
+ ]
+ },
+ {
+ "name": "Services",
+ "item": [
{
- "name": "Sales - Get Detail",
+ "name": "Get All Services",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/sales/{{saleId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Sales - Refund",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/sales/{{saleId}}/refunds",
+ "url": "{{baseUrl}}/api/v1/services",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"paymentMethod\": \"Card\",\n \"storeId\": \"{{storeId}}\",\n \"items\": [\n {\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 1\n }\n ]\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- }
+ ]
}
- }
- ]
- },
- {
- "name": "Analytics (Admin)",
- "item": [
+ },
{
- "name": "Dashboard",
+ "name": "Get Service by ID",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/analytics/dashboard?days=30&top=10",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- }
- ]
- },
- {
- "name": "Chat (REST) + WebSocket (/ws)",
- "item": [
- {
- "name": "Chat - Create Room (Customer)",
- "request": {
- "method": "POST",
- "url": "{{baseUrl}}/api/v1/chat/rooms",
+ "url": "{{baseUrl}}/api/v1/services/1",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
- ],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- },
- "body": {
- "mode": "raw",
- "raw": "{\n \"topic\": \"Support\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
+ ]
+ }
+ },
+ {
+ "name": "Get Services Dropdown",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/dropdowns/services",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
}
- }
+ ]
}
},
{
- "name": "Chat - List Rooms (Customer)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/chat/rooms",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Chat - List Rooms (Staff/Admin)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/chat/rooms",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Chat - Room Messages (Customer)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/messages?limit=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Chat - Room Messages (Staff/Admin)",
- "request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/messages?limit=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
- }
- }
- },
- {
- "name": "Chat - Close Room (Staff/Admin)",
+ "name": "Create Service",
"request": {
"method": "POST",
- "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/close",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{staffToken}}",
- "type": "string"
- }
- ]
+ "url": "{{baseUrl}}/api/v1/services",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 75.0\n}"
}
}
},
{
- "name": "WebSocket Notes",
+ "name": "Update Service",
"request": {
- "method": "GET",
- "url": "{{baseUrl}}/ws",
- "header": [],
- "description": "WebSocket endpoint for live chat."
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/services/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 80.0\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Service",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/services/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Services",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/services",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1\n ]\n}"
+ }
}
}
]
},
{
- "name": "Public Assets",
+ "name": "Categories",
"item": [
{
- "name": "Get Staff Avatar (public)",
+ "name": "Get All Categories",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/staff/{{employeeId}}/avatar",
- "header": [],
- "description": "Public endpoint. Accessible without auth."
+ "url": "{{baseUrl}}/api/v1/categories",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Category by ID",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/categories/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Categories Dropdown",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/dropdowns/categories",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create Category",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/categories",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Update Category",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/categories/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Category",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/categories/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Categories",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/categories",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1\n ]\n}"
+ }
}
}
]
},
{
- "name": "Customer Browse (optional)",
+ "name": "Appointments",
"item": [
{
- "name": "Pets - List (customer)",
+ "name": "Check Appointment Availability",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/pets?q=&page=0&size=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
+ "url": "{{baseUrl}}/api/v1/appointments/availability?storeId=1&serviceId=1&date=2026-03-15",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "List Appointments",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/appointments",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Appointment",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/appointments/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create Appointment",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/appointments",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"customerId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"10:30:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1, 2]\n}"
}
}
},
{
- "name": "Pets - Detail (customer)",
+ "name": "Update Appointment",
"request": {
- "method": "GET",
- "url": "{{baseUrl}}/api/v1/pets/{{petId}}",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/appointments/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"customerId\": 1,\n \"serviceId\": 2,\n \"appointmentDate\": \"2026-03-20\",\n \"appointmentTime\": \"14:00:00\",\n \"appointmentStatus\": \"Completed\",\n \"petIds\": [1]\n}"
}
}
},
{
- "name": "Services - List (customer)",
+ "name": "Delete Appointment",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/appointments/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Appointments",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/appointments",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1\n ]\n}"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Adoptions",
+ "item": [
+ {
+ "name": "List Adoptions",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/services?q=&page=0&size=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
+ "url": "{{baseUrl}}/api/v1/adoptions",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Adoption",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/adoptions/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create Adoption",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/adoptions",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\",\n \"adoptionStatus\": \"Pending\"\n}"
}
}
},
{
- "name": "Stores - List (customer)",
+ "name": "Update Adoption",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/adoptions/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\",\n \"adoptionStatus\": \"Completed\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Adoption",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/adoptions/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Adoptions",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/adoptions",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1\n ]\n}"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Refunds",
+ "item": [
+ {
+ "name": "Create Refund",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/refunds",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"saleId\": 1,\n \"reason\": \"Defective product\"\n}"
+ }
+ }
+ },
+ {
+ "name": "List Refunds",
"request": {
"method": "GET",
- "url": "{{baseUrl}}/api/v1/stores?q=&page=0&size=50",
- "header": [],
- "auth": {
- "type": "bearer",
- "bearer": [
- {
- "key": "token",
- "value": "{{customerToken}}",
- "type": "string"
- }
- ]
+ "url": "{{baseUrl}}/api/v1/refunds",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Refund",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/refunds/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Update Refund",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/refunds/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"status\": \"APPROVED\"\n}"
}
}
+ },
+ {
+ "name": "Delete Refund",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/refunds/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "Chat",
+ "item": [
+ {
+ "name": "Create Conversation",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/chat/conversations",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"message\": \"I need help\"\n}"
+ }
+ }
+ },
+ {
+ "name": "List Conversations",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/chat/conversations",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Conversation",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/chat/conversations/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Send Message",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"content\": \"Hello\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Get Messages",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{customerToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "Customers",
+ "item": [
+ {
+ "name": "Get Customers Dropdown",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/dropdowns/customers",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "List Customers",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/customers",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Customer",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/customers/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create Customer",
+ "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\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john.doe@example.com\",\n \"phone\": \"555-123-4567\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Update Customer",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/customers/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"email\": \"jane.doe@example.com\",\n \"phone\": \"555-987-6543\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Customer",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/customers/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{staffToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "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 1\n ]\n}"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Users",
+ "item": [
+ {
+ "name": "List Users",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/users",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get User",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/users/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create User",
+ "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 \"username\": \"newuser\",\n \"password\": \"password123\",\n \"fullName\": \"New User\",\n \"email\": \"newuser@petshop.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}"
+ }
+ }
+ },
+ {
+ "name": "Update User",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/users/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"username\": \"user1\",\n \"password\": \"newpassword123\",\n \"fullName\": \"Updated User\",\n \"email\": \"user1@petshop.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete User",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/users/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Users",
+ "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 1\n ]\n}"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Stores",
+ "item": [
+ {
+ "name": "Get Stores Dropdown",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/dropdowns/stores",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ }
+ ]
+ }
+ },
+ {
+ "name": "List Stores",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/stores",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Store",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/stores/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create Store",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/stores",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"storeName\": \"New Pet Shop\",\n \"address\": \"123 Main Street\",\n \"phone\": \"555-111-2222\",\n \"email\": \"newstore@petshop.com\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Update Store",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/stores/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"storeName\": \"Updated Pet Shop\",\n \"address\": \"456 Oak Avenue\",\n \"phone\": \"555-333-4444\",\n \"email\": \"updated@petshop.com\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Store",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/stores/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Stores",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/stores",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1\n ]\n}"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Inventory",
+ "item": [
+ {
+ "name": "List Inventory",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/inventory",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Inventory Item",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/inventory/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "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\": 100\n}"
+ }
+ }
+ },
+ {
+ "name": "Update Inventory",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/inventory/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"prodId\": 1,\n \"quantity\": 150\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Inventory",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/inventory/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Inventory",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/inventory",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1\n ]\n}"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Suppliers",
+ "item": [
+ {
+ "name": "List Suppliers",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/suppliers",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Supplier",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/suppliers/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Create Supplier",
+ "request": {
+ "method": "POST",
+ "url": "{{baseUrl}}/api/v1/suppliers",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"supCompany\": \"New Supplier Inc\",\n \"supContactFirstName\": \"John\",\n \"supContactLastName\": \"Smith\",\n \"supEmail\": \"john@newsupplier.com\",\n \"supPhone\": \"555-555-5555\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Update Supplier",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/suppliers/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"supCompany\": \"Updated Supplier Co\",\n \"supContactFirstName\": \"Jane\",\n \"supContactLastName\": \"Doe\",\n \"supEmail\": \"jane@updatedsupplier.com\",\n \"supPhone\": \"555-666-7777\"\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Supplier",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/suppliers/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Suppliers",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/suppliers",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"ids\": [\n 1\n ]\n}"
+ }
+ }
+ },
+ {
+ "name": "Get Suppliers Dropdown",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/dropdowns/suppliers",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "Purchase Orders",
+ "item": [
+ {
+ "name": "List Purchase Orders",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/purchase-orders",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Purchase Order",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/purchase-orders/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "name": "Product-Suppliers",
+ "item": [
+ {
+ "name": "List Product Suppliers",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/product-suppliers",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Get Product Supplier",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/product-suppliers/1/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "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\": 2,\n \"cost\": 35.00\n}"
+ }
+ }
+ },
+ {
+ "name": "Update Product Supplier",
+ "request": {
+ "method": "PUT",
+ "url": "{{baseUrl}}/api/v1/product-suppliers/1/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"productId\": 1,\n \"supplierId\": 2,\n \"cost\": 40.00\n}"
+ }
+ }
+ },
+ {
+ "name": "Delete Product Supplier",
+ "request": {
+ "method": "DELETE",
+ "url": "{{baseUrl}}/api/v1/product-suppliers/1/1",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Bulk Delete Product Suppliers",
+ "request": {
+ "method": "DELETE",
+ "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 \"keys\": [\n {\"productId\": 1, \"supplierId\": 2},\n {\"productId\": 3, \"supplierId\": 4}\n ]\n}"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Analytics",
+ "item": [
+ {
+ "name": "Analytics Dashboard",
+ "request": {
+ "method": "GET",
+ "url": "{{baseUrl}}/api/v1/analytics/dashboard",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{adminToken}}",
+ "type": "text"
+ }
+ ]
+ }
}
]
}
diff --git a/pom.xml b/pom.xml
index 47a732fe..5ad436e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,11 @@
spring-boot-starter-validation
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
com.mysql
mysql-connector-j
diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java
index a3db73ab..a9dd4b15 100644
--- a/src/main/java/com/petshop/backend/config/DataInitializer.java
+++ b/src/main/java/com/petshop/backend/config/DataInitializer.java
@@ -2,6 +2,7 @@ package com.petshop.backend.config;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.UserRepository;
+import com.petshop.backend.service.UserBusinessLinkageService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@@ -11,28 +12,138 @@ public class DataInitializer implements CommandLineRunner {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
+ private final UserBusinessLinkageService userBusinessLinkageService;
- public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder) {
+ public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
+ this.userBusinessLinkageService = userBusinessLinkageService;
}
@Override
public void run(String... args) {
- if (userRepository.findByUsername("admin").isEmpty()) {
- User admin = new User();
+ System.out.println("==== DataInitializer: Starting user creation ====");
+
+ User admin = userRepository.findByUsername("admin").orElse(null);
+ if (admin == null) {
+ System.out.println("Creating admin user...");
+ admin = new User();
admin.setUsername("admin");
admin.setPassword(passwordEncoder.encode("admin123"));
+ admin.setEmail("admin@petshop.com");
+ admin.setFullName("Admin User");
admin.setRole(User.Role.ADMIN);
- userRepository.save(admin);
+ admin.setActive(true);
+ admin = userRepository.save(admin);
+ System.out.println("Admin user created successfully");
+ } else {
+ System.out.println("Admin user already exists");
+ // Normalize missing fields if needed
+ boolean updated = false;
+ if (admin.getFullName() == null || admin.getFullName().isEmpty()) {
+ admin.setFullName("Admin User");
+ updated = true;
+ }
+ if (admin.getEmail() == null || admin.getEmail().isEmpty()) {
+ admin.setEmail("admin@petshop.com");
+ updated = true;
+ }
+ if (admin.getActive() == null) {
+ admin.setActive(true);
+ updated = true;
+ }
+ if (admin.getRole() == null) {
+ admin.setRole(User.Role.ADMIN);
+ updated = true;
+ }
+ if (updated) {
+ admin = userRepository.save(admin);
+ System.out.println("Admin user normalized");
+ }
}
+ // Ensure linked employee
+ userBusinessLinkageService.ensureLinkedEmployee(admin);
- if (userRepository.findByUsername("staff").isEmpty()) {
- User staff = new User();
+ User staff = userRepository.findByUsername("staff").orElse(null);
+ if (staff == null) {
+ System.out.println("Creating staff user...");
+ staff = new User();
staff.setUsername("staff");
staff.setPassword(passwordEncoder.encode("staff123"));
+ staff.setEmail("staff@petshop.com");
+ staff.setFullName("Staff User");
staff.setRole(User.Role.STAFF);
- userRepository.save(staff);
+ staff.setActive(true);
+ staff = userRepository.save(staff);
+ System.out.println("Staff user created successfully");
+ } else {
+ System.out.println("Staff user already exists");
+ // Normalize missing fields if needed
+ boolean updated = false;
+ if (staff.getFullName() == null || staff.getFullName().isEmpty()) {
+ staff.setFullName("Staff User");
+ updated = true;
+ }
+ if (staff.getEmail() == null || staff.getEmail().isEmpty()) {
+ staff.setEmail("staff@petshop.com");
+ updated = true;
+ }
+ if (staff.getActive() == null) {
+ staff.setActive(true);
+ updated = true;
+ }
+ if (staff.getRole() == null) {
+ staff.setRole(User.Role.STAFF);
+ updated = true;
+ }
+ if (updated) {
+ staff = userRepository.save(staff);
+ System.out.println("Staff user normalized");
+ }
}
+ // Ensure linked employee
+ userBusinessLinkageService.ensureLinkedEmployee(staff);
+
+ User customer = userRepository.findByUsername("customer").orElse(null);
+ if (customer == null) {
+ System.out.println("Creating customer user...");
+ customer = new User();
+ customer.setUsername("customer");
+ customer.setPassword(passwordEncoder.encode("customer123"));
+ customer.setEmail("customer@petshop.com");
+ customer.setFullName("Test Customer");
+ customer.setRole(User.Role.CUSTOMER);
+ customer.setActive(true);
+ customer = userRepository.save(customer);
+ System.out.println("Customer user created successfully");
+ } else {
+ System.out.println("Customer user already exists");
+ // Normalize missing fields if needed
+ boolean updated = false;
+ if (customer.getFullName() == null || customer.getFullName().isEmpty()) {
+ customer.setFullName("Test Customer");
+ updated = true;
+ }
+ if (customer.getEmail() == null || customer.getEmail().isEmpty()) {
+ customer.setEmail("customer@petshop.com");
+ updated = true;
+ }
+ if (customer.getActive() == null) {
+ customer.setActive(true);
+ updated = true;
+ }
+ if (customer.getRole() == null) {
+ customer.setRole(User.Role.CUSTOMER);
+ updated = true;
+ }
+ if (updated) {
+ customer = userRepository.save(customer);
+ System.out.println("Customer user normalized");
+ }
+ }
+ // Ensure linked customer
+ userBusinessLinkageService.ensureLinkedCustomer(customer);
+
+ System.out.println("==== DataInitializer: Completed ====");
}
}
diff --git a/src/main/java/com/petshop/backend/config/WebSocketConfig.java b/src/main/java/com/petshop/backend/config/WebSocketConfig.java
new file mode 100644
index 00000000..84944c25
--- /dev/null
+++ b/src/main/java/com/petshop/backend/config/WebSocketConfig.java
@@ -0,0 +1,25 @@
+package com.petshop.backend.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.simp.config.MessageBrokerRegistry;
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+
+@Configuration
+@EnableWebSocketMessageBroker
+public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+
+ @Override
+ public void configureMessageBroker(MessageBrokerRegistry config) {
+ config.enableSimpleBroker("/topic", "/queue");
+ config.setApplicationDestinationPrefixes("/app");
+ }
+
+ @Override
+ public void registerStompEndpoints(StompEndpointRegistry registry) {
+ registry.addEndpoint("/ws/chat")
+ .setAllowedOriginPatterns("*")
+ .withSockJS();
+ }
+}
diff --git a/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java
index 30da7d5e..17790070 100644
--- a/src/main/java/com/petshop/backend/controller/AdoptionController.java
+++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java
@@ -3,12 +3,19 @@ package com.petshop.backend.controller;
import com.petshop.backend.dto.adoption.AdoptionRequest;
import com.petshop.backend.dto.adoption.AdoptionResponse;
import com.petshop.backend.dto.common.BulkDeleteRequest;
+import com.petshop.backend.entity.Customer;
+import com.petshop.backend.repository.CustomerRepository;
+import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.AdoptionService;
+import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@RestController
@@ -16,29 +23,61 @@ import org.springframework.web.bind.annotation.*;
public class AdoptionController {
private final AdoptionService adoptionService;
+ private final UserRepository userRepository;
+ private final CustomerRepository customerRepository;
- public AdoptionController(AdoptionService adoptionService) {
+ public AdoptionController(AdoptionService adoptionService, UserRepository userRepository, CustomerRepository customerRepository) {
this.adoptionService = adoptionService;
+ this.userRepository = userRepository;
+ this.customerRepository = customerRepository;
}
@GetMapping
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity> getAllAdoptions(
@RequestParam(required = false) String q,
Pageable pageable) {
- return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable));
+ 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")) {
+ Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
+ customerId = customer.getCustomerId();
+ }
+
+ return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable, customerId));
}
@GetMapping("/{id}")
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity getAdoptionById(@PathVariable Long id) {
- return ResponseEntity.ok(adoptionService.getAdoptionById(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")) {
+ Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
+ customerId = customer.getCustomerId();
+ }
+
+ return ResponseEntity.ok(adoptionService.getAdoptionById(id, customerId));
}
@PostMapping
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity createAdoption(@Valid @RequestBody AdoptionRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request));
}
@PutMapping("/{id}")
+ @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity updateAdoption(
@PathVariable Long id,
@Valid @RequestBody AdoptionRequest request) {
@@ -46,12 +85,14 @@ public class AdoptionController {
}
@DeleteMapping("/{id}")
+ @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity deleteAdoption(@PathVariable Long id) {
adoptionService.deleteAdoption(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
+ @PreAuthorize("hasRole('ADMIN')")
public ResponseEntity bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) {
adoptionService.bulkDeleteAdoptions(request);
return ResponseEntity.noContent().build();
diff --git a/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java
index 6c9f8fa4..20e0c83d 100644
--- a/src/main/java/com/petshop/backend/controller/AppointmentController.java
+++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java
@@ -3,12 +3,19 @@ package com.petshop.backend.controller;
import com.petshop.backend.dto.appointment.AppointmentRequest;
import com.petshop.backend.dto.appointment.AppointmentResponse;
import com.petshop.backend.dto.common.BulkDeleteRequest;
+import com.petshop.backend.entity.Customer;
+import com.petshop.backend.repository.CustomerRepository;
+import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.service.AppointmentService;
+import com.petshop.backend.util.AuthenticationHelper;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
@@ -19,29 +26,61 @@ import java.util.List;
public class AppointmentController {
private final AppointmentService appointmentService;
+ private final UserRepository userRepository;
+ private final CustomerRepository customerRepository;
- public AppointmentController(AppointmentService appointmentService) {
+ public AppointmentController(AppointmentService appointmentService, UserRepository userRepository, CustomerRepository customerRepository) {
this.appointmentService = appointmentService;
+ this.userRepository = userRepository;
+ this.customerRepository = customerRepository;
}
@GetMapping
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity> getAllAppointments(
@RequestParam(required = false) String q,
Pageable pageable) {
- return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable));
+ 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")) {
+ Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
+ customerId = customer.getCustomerId();
+ }
+
+ return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable, customerId));
}
@GetMapping("/{id}")
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity getAppointmentById(@PathVariable Long id) {
- return ResponseEntity.ok(appointmentService.getAppointmentById(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")) {
+ Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository);
+ customerId = customer.getCustomerId();
+ }
+
+ return ResponseEntity.ok(appointmentService.getAppointmentById(id, customerId));
}
@PostMapping
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
public ResponseEntity createAppointment(@Valid @RequestBody AppointmentRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(appointmentService.createAppointment(request));
}
@PutMapping("/{id}")
+ @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity updateAppointment(
@PathVariable Long id,
@Valid @RequestBody AppointmentRequest request) {
@@ -49,12 +88,14 @@ public class AppointmentController {
}
@DeleteMapping("/{id}")
+ @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity deleteAppointment(@PathVariable Long id) {
appointmentService.deleteAppointment(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
+ @PreAuthorize("hasRole('ADMIN')")
public ResponseEntity bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) {
appointmentService.bulkDeleteAppointments(request);
return ResponseEntity.noContent().build();
diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java
index d0a6cc39..22f53420 100644
--- a/src/main/java/com/petshop/backend/controller/AuthController.java
+++ b/src/main/java/com/petshop/backend/controller/AuthController.java
@@ -1,11 +1,16 @@
package com.petshop.backend.controller;
+import com.petshop.backend.dto.auth.AvatarUploadResponse;
import com.petshop.backend.dto.auth.LoginRequest;
import com.petshop.backend.dto.auth.LoginResponse;
+import com.petshop.backend.dto.auth.ProfileUpdateRequest;
+import com.petshop.backend.dto.auth.RegisterRequest;
+import com.petshop.backend.dto.auth.RegisterResponse;
import com.petshop.backend.dto.auth.UserInfoResponse;
import com.petshop.backend.entity.User;
import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.JwtUtil;
+import com.petshop.backend.service.UserBusinessLinkageService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -18,9 +23,17 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
+import java.util.UUID;
@RestController
@RequestMapping("/api/v1/auth")
@@ -30,12 +43,58 @@ public class AuthController {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final PasswordEncoder passwordEncoder;
+ private final UserBusinessLinkageService userBusinessLinkageService;
- public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder) {
+ public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) {
this.authenticationManager = authenticationManager;
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
this.passwordEncoder = passwordEncoder;
+ this.userBusinessLinkageService = userBusinessLinkageService;
+ }
+
+ @PostMapping("/register")
+ public ResponseEntity> register(@Valid @RequestBody RegisterRequest request) {
+ if (userRepository.findByUsername(request.getUsername()).isPresent()) {
+ Map error = new HashMap<>();
+ error.put("message", "Username already exists");
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
+ }
+
+ if (userRepository.findByEmail(request.getEmail()).isPresent()) {
+ Map error = new HashMap<>();
+ error.put("message", "Email already exists");
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
+ }
+
+ User user = new User();
+ user.setUsername(request.getUsername());
+ user.setPassword(passwordEncoder.encode(request.getPassword()));
+ user.setEmail(request.getEmail());
+ user.setFullName(request.getFullName());
+ user.setRole(User.Role.CUSTOMER);
+ user.setActive(true);
+
+ User savedUser = userRepository.save(user);
+
+ // Create or link customer record
+ userBusinessLinkageService.ensureLinkedCustomer(savedUser);
+
+ UserDetails userDetails = new org.springframework.security.core.userdetails.User(
+ savedUser.getUsername(),
+ savedUser.getPassword(),
+ java.util.Collections.emptyList()
+ );
+
+ String token = jwtUtil.generateToken(userDetails);
+
+ return ResponseEntity.status(HttpStatus.CREATED).body(new RegisterResponse(
+ savedUser.getId(),
+ savedUser.getUsername(),
+ savedUser.getEmail(),
+ savedUser.getRole().name(),
+ token
+ ));
}
@PostMapping("/login")
@@ -80,14 +139,162 @@ public class AuthController {
return ResponseEntity.ok(new UserInfoResponse(
user.getId(),
user.getUsername(),
+ user.getEmail(),
+ user.getFullName(),
+ user.getAvatarUrl(),
user.getRole().name()
));
}
+ @PutMapping("/me")
+ public ResponseEntity> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ String username = authentication.getName();
+
+ User user = userRepository.findByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+
+ if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) {
+ if (userRepository.findByUsername(request.getUsername()).isPresent()) {
+ Map error = new HashMap<>();
+ error.put("message", "Username already exists");
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
+ }
+ user.setUsername(request.getUsername());
+ }
+
+ if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
+ if (userRepository.findByEmail(request.getEmail()).isPresent()) {
+ Map error = new HashMap<>();
+ error.put("message", "Email already exists");
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
+ }
+ user.setEmail(request.getEmail());
+ }
+
+ if (request.getFullName() != null) {
+ user.setFullName(request.getFullName());
+ }
+
+ if (request.getPassword() != null && !request.getPassword().isEmpty()) {
+ user.setPassword(passwordEncoder.encode(request.getPassword()));
+ }
+
+ User updatedUser = userRepository.save(user);
+
+ return ResponseEntity.ok(new UserInfoResponse(
+ updatedUser.getId(),
+ updatedUser.getUsername(),
+ updatedUser.getEmail(),
+ updatedUser.getFullName(),
+ updatedUser.getAvatarUrl(),
+ updatedUser.getRole().name()
+ ));
+ }
+
+ @PostMapping("/me/avatar")
+ public ResponseEntity> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ String username = authentication.getName();
+
+ User user = userRepository.findByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+
+ if (file.isEmpty()) {
+ Map error = new HashMap<>();
+ error.put("message", "Please select a file to upload");
+ return ResponseEntity.badRequest().body(error);
+ }
+
+ if (file.getSize() > 5 * 1024 * 1024) {
+ Map error = new HashMap<>();
+ error.put("message", "File size must not exceed 5MB");
+ return ResponseEntity.badRequest().body(error);
+ }
+
+ String contentType = file.getContentType();
+ if (contentType == null || (!contentType.equals("image/jpeg") && !contentType.equals("image/png") && !contentType.equals("image/gif"))) {
+ Map error = new HashMap<>();
+ error.put("message", "Only JPG, PNG, and GIF images are allowed");
+ return ResponseEntity.badRequest().body(error);
+ }
+
+ try {
+ String uploadDir = "uploads/avatars";
+ File directory = new File(uploadDir);
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ String originalFilename = file.getOriginalFilename();
+ String extension = originalFilename != null && originalFilename.contains(".")
+ ? originalFilename.substring(originalFilename.lastIndexOf("."))
+ : ".jpg";
+ String filename = UUID.randomUUID().toString() + extension;
+ Path filePath = Paths.get(uploadDir, filename);
+
+ Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
+
+ String avatarUrl = "/uploads/avatars/" + filename;
+ user.setAvatarUrl(avatarUrl);
+ userRepository.save(user);
+
+ return ResponseEntity.ok(new AvatarUploadResponse(avatarUrl, "Avatar uploaded successfully"));
+
+ } catch (IOException e) {
+ Map error = new HashMap<>();
+ error.put("message", "Failed to upload avatar: " + e.getMessage());
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
+ }
+ }
+
+ @GetMapping("/me/avatar")
+ public ResponseEntity> getAvatar() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ String username = authentication.getName();
+
+ User user = userRepository.findByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+
+ if (user.getAvatarUrl() == null || user.getAvatarUrl().isEmpty()) {
+ Map error = new HashMap<>();
+ error.put("message", "No avatar uploaded");
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
+ }
+
+ Map response = new HashMap<>();
+ response.put("avatarUrl", user.getAvatarUrl());
+ return ResponseEntity.ok(response);
+ }
+
+ @DeleteMapping("/me/avatar")
+ public ResponseEntity> deleteAvatar() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ String username = authentication.getName();
+
+ User user = userRepository.findByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+
+ if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) {
+ try {
+ Path filePath = Paths.get("." + user.getAvatarUrl());
+ Files.deleteIfExists(filePath);
+ } catch (IOException e) {
+ }
+ user.setAvatarUrl(null);
+ userRepository.save(user);
+ }
+
+ Map response = new HashMap<>();
+ response.put("message", "Avatar deleted successfully");
+ return ResponseEntity.ok(response);
+ }
+
@PostMapping("/logout")
public ResponseEntity> logout() {
Map response = new HashMap<>();
response.put("message", "Logged out successfully");
+ response.put("note", "Token remains valid until expiration. Clear token from client storage.");
return ResponseEntity.ok(response);
}
}
diff --git a/src/main/java/com/petshop/backend/controller/CategoryController.java b/src/main/java/com/petshop/backend/controller/CategoryController.java
index ddd7b934..fb938dd9 100644
--- a/src/main/java/com/petshop/backend/controller/CategoryController.java
+++ b/src/main/java/com/petshop/backend/controller/CategoryController.java
@@ -9,6 +9,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@@ -34,11 +35,13 @@ public class CategoryController {
}
@PostMapping
+ @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity createCategory(@Valid @RequestBody CategoryRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(categoryService.createCategory(request));
}
@PutMapping("/{id}")
+ @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity updateCategory(
@PathVariable Long id,
@Valid @RequestBody CategoryRequest request) {
@@ -46,12 +49,14 @@ public class CategoryController {
}
@DeleteMapping("/{id}")
+ @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity deleteCategory(@PathVariable Long id) {
categoryService.deleteCategory(id);
return ResponseEntity.noContent().build();
}
@DeleteMapping
+ @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public ResponseEntity bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) {
categoryService.bulkDeleteCategories(request);
return ResponseEntity.noContent().build();
diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java
new file mode 100644
index 00000000..f503cedf
--- /dev/null
+++ b/src/main/java/com/petshop/backend/controller/ChatController.java
@@ -0,0 +1,83 @@
+package com.petshop.backend.controller;
+
+import com.petshop.backend.dto.chat.ConversationRequest;
+import com.petshop.backend.dto.chat.ConversationResponse;
+import com.petshop.backend.dto.chat.MessageRequest;
+import com.petshop.backend.dto.chat.MessageResponse;
+import com.petshop.backend.entity.User;
+import com.petshop.backend.repository.CustomerRepository;
+import com.petshop.backend.repository.UserRepository;
+import com.petshop.backend.service.ChatService;
+import jakarta.validation.Valid;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/v1/chat")
+public class ChatController {
+
+ private final ChatService chatService;
+ private final UserRepository userRepository;
+ private final CustomerRepository customerRepository;
+
+ public ChatController(ChatService chatService, UserRepository userRepository, CustomerRepository customerRepository) {
+ this.chatService = chatService;
+ this.userRepository = userRepository;
+ this.customerRepository = customerRepository;
+ }
+
+ private User getCurrentUser() {
+ UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+ return userRepository.findByUsername(userDetails.getUsername())
+ .orElseThrow(() -> new UsernameNotFoundException("User not found"));
+ }
+
+ @PostMapping("/conversations")
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
+ public ResponseEntity createConversation(@Valid @RequestBody ConversationRequest request) {
+ User user = getCurrentUser();
+ ConversationResponse response = chatService.createConversation(user.getId(), request);
+ return ResponseEntity.status(HttpStatus.CREATED).body(response);
+ }
+
+ @GetMapping("/conversations")
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
+ public ResponseEntity> getConversations() {
+ User user = getCurrentUser();
+ List conversations = chatService.getConversations(user.getId(), user.getRole());
+ return ResponseEntity.ok(conversations);
+ }
+
+ @GetMapping("/conversations/{id}")
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
+ public ResponseEntity getConversation(@PathVariable Long id) {
+ User user = getCurrentUser();
+ ConversationResponse conversation = chatService.getConversation(id, user.getId(), user.getRole());
+ return ResponseEntity.ok(conversation);
+ }
+
+ @PostMapping("/conversations/{id}/messages")
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
+ public ResponseEntity sendMessage(
+ @PathVariable Long id,
+ @Valid @RequestBody MessageRequest request) {
+ User user = getCurrentUser();
+ MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request);
+ return ResponseEntity.status(HttpStatus.CREATED).body(message);
+ }
+
+ @GetMapping("/conversations/{id}/messages")
+ @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
+ public ResponseEntity> getMessages(@PathVariable Long id) {
+ User user = getCurrentUser();
+ List messages = chatService.getMessages(id, user.getId(), user.getRole());
+ return ResponseEntity.ok(messages);
+ }
+}
diff --git a/src/main/java/com/petshop/backend/controller/CustomerController.java b/src/main/java/com/petshop/backend/controller/CustomerController.java
index 75bed4fc..f3ab880e 100644
--- a/src/main/java/com/petshop/backend/controller/CustomerController.java
+++ b/src/main/java/com/petshop/backend/controller/CustomerController.java
@@ -9,10 +9,12 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/customers")
+@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
public class CustomerController {
private final CustomerService customerService;
diff --git a/src/main/java/com/petshop/backend/controller/HealthController.java b/src/main/java/com/petshop/backend/controller/HealthController.java
new file mode 100644
index 00000000..8ee609c3
--- /dev/null
+++ b/src/main/java/com/petshop/backend/controller/HealthController.java
@@ -0,0 +1,18 @@
+package com.petshop.backend.controller;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/v1/health")
+public class HealthController {
+
+ @GetMapping
+ public ResponseEntity