From 22550055d3fc8d127e2424c258e343c5665fef6f Mon Sep 17 00:00:00 2001 From: boutmoun123 Date: Sun, 7 Jun 2026 14:25:24 +0300 Subject: [PATCH] feat: add notification badge counts --- ...a-Auth-Users-Posts.postman_collection.json | 760 ++++++++++++++ .../Oudelaa-Dashboard.postman_collection.json | 936 ++++++++++++++++++ .../Oudelaa-Mobile.postman_collection.json | 548 ++++++++++ .../dto/notification-query.dto.ts | 9 + .../notifications/notifications.controller.ts | 18 +- .../notifications/notifications.repository.ts | 13 + .../notifications.service.spec.ts | 145 +++ .../notifications/notifications.service.ts | 51 + 8 files changed, 2477 insertions(+), 3 deletions(-) diff --git a/postman/Oudelaa-Auth-Users-Posts.postman_collection.json b/postman/Oudelaa-Auth-Users-Posts.postman_collection.json index fe68c7a..c8f6c3e 100644 --- a/postman/Oudelaa-Auth-Users-Posts.postman_collection.json +++ b/postman/Oudelaa-Auth-Users-Posts.postman_collection.json @@ -4712,6 +4712,235 @@ } ] }, + { + "name": "Get Unread Notifications Count By Category", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category={{notificationCategory}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Get Grouped Unread Notification Counts", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-counts" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('total');", + "pm.expect(json).to.have.property('interactions');", + "pm.expect(json).to.have.property('messages');", + "pm.expect(json).to.have.property('follows');", + "pm.expect(json).to.have.property('followRequests');", + "pm.expect(json).to.have.property('collaboration');", + "pm.expect(json).to.have.property('system');", + "pm.environment.set('notificationUnreadCount', json.total);" + ] + } + } + ] + }, + { + "name": "Badge Count - Interactions", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=interactions" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'interactions');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Messages", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=messages" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'messages');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Follows", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=follows" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'follows');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Follow Requests", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=follow_requests" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'follow_requests');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Collaboration", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=collaboration" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'collaboration');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - System", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=system" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'system');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, { "name": "Get Target User Unread Notifications Count", "request": { @@ -7812,6 +8041,493 @@ } ] }, + { + "name": "Support", + "item": [ + { + "name": "Create Support Ticket", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "subject", + "value": "{{supportSubject}}", + "type": "text" + }, + { + "key": "message", + "value": "{{supportMessage}}", + "type": "text" + }, + { + "key": "category", + "value": "{{supportCategory}}", + "type": "text" + }, + { + "key": "priority", + "value": "{{supportPriority}}", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || json.ticket || (json.items && json.items[0]);", + "if (item) pm.environment.set('supportTicketId', item._id || item.id);", + "pm.test('Ticket created', function () { const json = pm.response.json(); pm.expect(json.item).to.have.property('status', 'open'); });" + ] + } + } + ] + }, + { + "name": "Create Support Ticket With Image", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "subject", + "value": "{{supportSubject}}", + "type": "text" + }, + { + "key": "message", + "value": "{{supportMessage}}", + "type": "text" + }, + { + "key": "category", + "value": "{{supportCategory}}", + "type": "text" + }, + { + "key": "priority", + "value": "{{supportPriority}}", + "type": "text" + }, + { + "key": "image", + "type": "file", + "src": "{{supportImagePath}}" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || json.ticket || (json.items && json.items[0]);", + "if (item) pm.environment.set('supportTicketId', item._id || item.id);", + "pm.test('Ticket created', function () { const json = pm.response.json(); pm.expect(json.item).to.have.property('status', 'open'); });" + ] + } + } + ] + }, + { + "name": "List My Support Tickets", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets?page=1&limit=20&sortOrder={{listSortOrder}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "if (json.items && json.items[0]) pm.environment.set('supportTicketId', json.items[0]._id || json.items[0].id);", + "pm.expect(json.items).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Get My Support Ticket", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets/{{supportTicketId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.ticket).to.exist;", + "pm.expect(json.messages).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Add Support Message", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportMessage}}", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Add Support Message With Image", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportMessage}}", + "type": "text" + }, + { + "key": "image", + "type": "file", + "src": "{{supportImagePath}}" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Close My Support Ticket", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets/{{supportTicketId}}/close" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.item).to.have.property('status', 'closed');" + ] + } + } + ] + }, + { + "name": "Admin List Support Tickets", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets?page=1&limit=20&status={{supportStatus}}&category={{supportCategory}}&priority={{supportPriority}}&sortOrder={{listSortOrder}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "if (json.items && json.items[0]) pm.environment.set('supportTicketId', json.items[0]._id || json.items[0].id);", + "pm.expect(json.items).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Admin Get Support Ticket", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.ticket).to.exist;", + "pm.expect(json.ticket.user).to.exist;", + "pm.expect(json.messages).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Admin Reply To Ticket", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportReplyMessage}}", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Admin Reply With Image", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportReplyMessage}}", + "type": "text" + }, + { + "key": "image", + "type": "file", + "src": "{{supportImagePath}}" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Admin Update Support Ticket Status", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/status", + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"{{supportStatus}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.item).to.have.property('status');" + ] + } + } + ] + }, + { + "name": "Admin Assign Support Ticket", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/assign", + "body": { + "mode": "raw", + "raw": "{\n \"adminId\": \"{{supportAdminId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.item).to.exist;" + ] + } + } + ] + } + ] + }, { "name": "Health", "item": [ @@ -9022,6 +9738,50 @@ { "key": "collaborationAttachmentType", "value": "audio" + }, + { + "key": "supportTicketId", + "value": "" + }, + { + "key": "supportMessageId", + "value": "" + }, + { + "key": "supportSubject", + "value": "????? ?? ????? ??? ??? MP3" + }, + { + "key": "supportMessage", + "value": "????? ??? ????? ???? ?? ??? ??? ????" + }, + { + "key": "supportReplyMessage", + "value": "?? ?????? ??????? ????? ??? ???????." + }, + { + "key": "supportCategory", + "value": "technical" + }, + { + "key": "supportPriority", + "value": "normal" + }, + { + "key": "supportStatus", + "value": "in_progress" + }, + { + "key": "supportImagePath", + "value": "" + }, + { + "key": "supportAdminId", + "value": "" + }, + { + "key": "notificationCategory", + "value": "interactions" } ] } diff --git a/postman/Oudelaa-Dashboard.postman_collection.json b/postman/Oudelaa-Dashboard.postman_collection.json index 6f1baad..f4270c6 100644 --- a/postman/Oudelaa-Dashboard.postman_collection.json +++ b/postman/Oudelaa-Dashboard.postman_collection.json @@ -1258,6 +1258,235 @@ ] } } + }, + { + "name": "Get Unread Notifications Count By Category", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category={{notificationCategory}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Get Grouped Unread Notification Counts", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-counts" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('total');", + "pm.expect(json).to.have.property('interactions');", + "pm.expect(json).to.have.property('messages');", + "pm.expect(json).to.have.property('follows');", + "pm.expect(json).to.have.property('followRequests');", + "pm.expect(json).to.have.property('collaboration');", + "pm.expect(json).to.have.property('system');", + "pm.environment.set('notificationUnreadCount', json.total);" + ] + } + } + ] + }, + { + "name": "Badge Count - Interactions", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=interactions" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'interactions');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Messages", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=messages" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'messages');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Follows", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=follows" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'follows');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Follow Requests", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=follow_requests" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'follow_requests');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Collaboration", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=collaboration" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'collaboration');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - System", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=system" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'system');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] } ] }, @@ -2766,6 +2995,223 @@ } ] }, + { + "name": "Support Admin", + "item": [ + { + "name": "Admin List Support Tickets", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets?page=1&limit=20&status={{supportStatus}}&category={{supportCategory}}&priority={{supportPriority}}&sortOrder={{listSortOrder}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "if (json.items && json.items[0]) pm.environment.set('supportTicketId', json.items[0]._id || json.items[0].id);", + "pm.expect(json.items).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Admin Get Support Ticket", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.ticket).to.exist;", + "pm.expect(json.ticket.user).to.exist;", + "pm.expect(json.messages).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Admin Reply To Ticket", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportReplyMessage}}", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Admin Reply With Image", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportReplyMessage}}", + "type": "text" + }, + { + "key": "image", + "type": "file", + "src": "{{supportImagePath}}" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Admin Update Support Ticket Status", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/status", + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"{{supportStatus}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.item).to.have.property('status');" + ] + } + } + ] + }, + { + "name": "Admin Assign Support Ticket", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/assign", + "body": { + "mode": "raw", + "raw": "{\n \"adminId\": \"{{supportAdminId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.item).to.exist;" + ] + } + } + ] + } + ] + }, { "name": "Audit", "item": [ @@ -3935,6 +4381,235 @@ } } }, + { + "name": "Get Unread Notifications Count By Category", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category={{notificationCategory}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Get Grouped Unread Notification Counts", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-counts" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('total');", + "pm.expect(json).to.have.property('interactions');", + "pm.expect(json).to.have.property('messages');", + "pm.expect(json).to.have.property('follows');", + "pm.expect(json).to.have.property('followRequests');", + "pm.expect(json).to.have.property('collaboration');", + "pm.expect(json).to.have.property('system');", + "pm.environment.set('notificationUnreadCount', json.total);" + ] + } + } + ] + }, + { + "name": "Badge Count - Interactions", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=interactions" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'interactions');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Messages", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=messages" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'messages');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Follows", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=follows" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'follows');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Follow Requests", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=follow_requests" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'follow_requests');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Collaboration", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=collaboration" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'collaboration');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - System", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=system" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'system');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, { "name": "Mark Notification Read", "request": { @@ -4009,6 +4684,223 @@ } ] }, + { + "name": "Support Tickets", + "item": [ + { + "name": "Admin List Support Tickets", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets?page=1&limit=20&status={{supportStatus}}&category={{supportCategory}}&priority={{supportPriority}}&sortOrder={{listSortOrder}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "if (json.items && json.items[0]) pm.environment.set('supportTicketId', json.items[0]._id || json.items[0].id);", + "pm.expect(json.items).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Admin Get Support Ticket", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.ticket).to.exist;", + "pm.expect(json.ticket.user).to.exist;", + "pm.expect(json.messages).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Admin Reply To Ticket", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportReplyMessage}}", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Admin Reply With Image", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportReplyMessage}}", + "type": "text" + }, + { + "key": "image", + "type": "file", + "src": "{{supportImagePath}}" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Admin Update Support Ticket Status", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/status", + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"{{supportStatus}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.item).to.have.property('status');" + ] + } + } + ] + }, + { + "name": "Admin Assign Support Ticket", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/support/admin/tickets/{{supportTicketId}}/assign", + "body": { + "mode": "raw", + "raw": "{\n \"adminId\": \"{{supportAdminId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.item).to.exist;" + ] + } + } + ] + } + ] + }, { "name": "Collaboration Requests", "item": [ @@ -4640,6 +5532,50 @@ { "key": "collaborationAttachmentType", "value": "audio" + }, + { + "key": "supportTicketId", + "value": "" + }, + { + "key": "supportMessageId", + "value": "" + }, + { + "key": "supportSubject", + "value": "????? ?? ????? ??? ??? MP3" + }, + { + "key": "supportMessage", + "value": "????? ??? ????? ???? ?? ??? ??? ????" + }, + { + "key": "supportReplyMessage", + "value": "?? ?????? ??????? ????? ??? ???????." + }, + { + "key": "supportCategory", + "value": "technical" + }, + { + "key": "supportPriority", + "value": "normal" + }, + { + "key": "supportStatus", + "value": "in_progress" + }, + { + "key": "supportImagePath", + "value": "" + }, + { + "key": "supportAdminId", + "value": "" + }, + { + "key": "notificationCategory", + "value": "interactions" } ] } diff --git a/postman/Oudelaa-Mobile.postman_collection.json b/postman/Oudelaa-Mobile.postman_collection.json index d6a5ea7..0358bb0 100644 --- a/postman/Oudelaa-Mobile.postman_collection.json +++ b/postman/Oudelaa-Mobile.postman_collection.json @@ -4255,6 +4255,235 @@ } ] }, + { + "name": "Get Unread Notifications Count By Category", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category={{notificationCategory}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Get Grouped Unread Notification Counts", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-counts" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('total');", + "pm.expect(json).to.have.property('interactions');", + "pm.expect(json).to.have.property('messages');", + "pm.expect(json).to.have.property('follows');", + "pm.expect(json).to.have.property('followRequests');", + "pm.expect(json).to.have.property('collaboration');", + "pm.expect(json).to.have.property('system');", + "pm.environment.set('notificationUnreadCount', json.total);" + ] + } + } + ] + }, + { + "name": "Badge Count - Interactions", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=interactions" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'interactions');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Messages", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=messages" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'messages');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Follows", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=follows" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'follows');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Follow Requests", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=follow_requests" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'follow_requests');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - Collaboration", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=collaboration" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'collaboration');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, + { + "name": "Badge Count - System", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count?category=system" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('unreadCount');", + "pm.expect(json).to.have.property('category', 'system');", + "pm.environment.set('notificationUnreadCount', json.unreadCount);" + ] + } + } + ] + }, { "name": "Mark Notification Read", "request": { @@ -5823,6 +6052,281 @@ } ] }, + { + "name": "Support", + "item": [ + { + "name": "Create Support Ticket", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "subject", + "value": "{{supportSubject}}", + "type": "text" + }, + { + "key": "message", + "value": "{{supportMessage}}", + "type": "text" + }, + { + "key": "category", + "value": "{{supportCategory}}", + "type": "text" + }, + { + "key": "priority", + "value": "{{supportPriority}}", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || json.ticket || (json.items && json.items[0]);", + "if (item) pm.environment.set('supportTicketId', item._id || item.id);", + "pm.test('Ticket created', function () { const json = pm.response.json(); pm.expect(json.item).to.have.property('status', 'open'); });" + ] + } + } + ] + }, + { + "name": "Create Support Ticket With Image", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "subject", + "value": "{{supportSubject}}", + "type": "text" + }, + { + "key": "message", + "value": "{{supportMessage}}", + "type": "text" + }, + { + "key": "category", + "value": "{{supportCategory}}", + "type": "text" + }, + { + "key": "priority", + "value": "{{supportPriority}}", + "type": "text" + }, + { + "key": "image", + "type": "file", + "src": "{{supportImagePath}}" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || json.ticket || (json.items && json.items[0]);", + "if (item) pm.environment.set('supportTicketId', item._id || item.id);", + "pm.test('Ticket created', function () { const json = pm.response.json(); pm.expect(json.item).to.have.property('status', 'open'); });" + ] + } + } + ] + }, + { + "name": "List My Support Tickets", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets?page=1&limit=20&sortOrder={{listSortOrder}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "if (json.items && json.items[0]) pm.environment.set('supportTicketId', json.items[0]._id || json.items[0].id);", + "pm.expect(json.items).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Get My Support Ticket", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets/{{supportTicketId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.ticket).to.exist;", + "pm.expect(json.messages).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Add Support Message", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportMessage}}", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Add Support Message With Image", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets/{{supportTicketId}}/messages", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "message", + "value": "{{supportMessage}}", + "type": "text" + }, + { + "key": "image", + "type": "file", + "src": "{{supportImagePath}}" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "const item = json.item || (json.messages && json.messages[0]);", + "if (item) pm.environment.set('supportMessageId', item._id || item.id);" + ] + } + } + ] + }, + { + "name": "Close My Support Ticket", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/support/tickets/{{supportTicketId}}/close" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Request succeeded', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201]); });", + "const json = pm.response.json();", + "pm.expect(json.item).to.have.property('status', 'closed');" + ] + } + } + ] + } + ] + }, { "name": "Health", "item": [ @@ -6450,6 +6954,50 @@ "key": "deviceId", "value": "postman-device-android", "type": "string" + }, + { + "key": "supportTicketId", + "value": "" + }, + { + "key": "supportMessageId", + "value": "" + }, + { + "key": "supportSubject", + "value": "????? ?? ????? ??? ??? MP3" + }, + { + "key": "supportMessage", + "value": "????? ??? ????? ???? ?? ??? ??? ????" + }, + { + "key": "supportReplyMessage", + "value": "?? ?????? ??????? ????? ??? ???????." + }, + { + "key": "supportCategory", + "value": "technical" + }, + { + "key": "supportPriority", + "value": "normal" + }, + { + "key": "supportStatus", + "value": "in_progress" + }, + { + "key": "supportImagePath", + "value": "" + }, + { + "key": "supportAdminId", + "value": "" + }, + { + "key": "notificationCategory", + "value": "interactions" } ] } diff --git a/src/modules/notifications/dto/notification-query.dto.ts b/src/modules/notifications/dto/notification-query.dto.ts index 7b49896..93c74fa 100644 --- a/src/modules/notifications/dto/notification-query.dto.ts +++ b/src/modules/notifications/dto/notification-query.dto.ts @@ -10,9 +10,18 @@ export const NOTIFICATION_CATEGORIES = [ 'messages', 'follows', 'follow_requests', + 'collaboration', + 'system', ] as const; export type NotificationCategory = (typeof NOTIFICATION_CATEGORIES)[number]; +export class NotificationUnreadCountQueryDto { + @ApiPropertyOptional({ enum: NOTIFICATION_CATEGORIES }) + @IsOptional() + @IsEnum(NOTIFICATION_CATEGORIES) + category?: NotificationCategory; +} + export class NotificationQueryDto extends PaginationQueryDto { @ApiPropertyOptional({ default: false }) @IsOptional() diff --git a/src/modules/notifications/notifications.controller.ts b/src/modules/notifications/notifications.controller.ts index 3b098eb..91767b0 100644 --- a/src/modules/notifications/notifications.controller.ts +++ b/src/modules/notifications/notifications.controller.ts @@ -7,7 +7,10 @@ import { SuperAdminJwtAuthGuard } from '../../common/guards/super-admin-jwt-auth import { SuperAdminPermissionsGuard } from '../../common/guards/superadmin-permissions.guard'; import { JwtPayload } from '../../common/interfaces/jwt-payload.interface'; import { SUPERADMIN_PERMISSIONS } from '../superadmin/superadmin-permissions'; -import { NotificationQueryDto } from './dto/notification-query.dto'; +import { + NotificationQueryDto, + NotificationUnreadCountQueryDto, +} from './dto/notification-query.dto'; import { NotificationsService } from './notifications.service'; @ApiTags('Notifications') @@ -32,8 +35,17 @@ export class NotificationsController { @UseGuards(JwtAuthGuard) @Get('unread-count') - async getUnreadCount(@CurrentUser() user: JwtPayload) { - return this.notificationsService.getUnreadCount(user.sub); + async getUnreadCount( + @CurrentUser() user: JwtPayload, + @Query() query: NotificationUnreadCountQueryDto, + ) { + return this.notificationsService.getUnreadCountByCategory(user.sub, query.category); + } + + @UseGuards(JwtAuthGuard) + @Get('unread-counts') + async getUnreadCounts(@CurrentUser() user: JwtPayload) { + return this.notificationsService.getUnreadCounts(user.sub); } @UseGuards(JwtAuthGuard) diff --git a/src/modules/notifications/notifications.repository.ts b/src/modules/notifications/notifications.repository.ts index 279a453..5bed8d1 100644 --- a/src/modules/notifications/notifications.repository.ts +++ b/src/modules/notifications/notifications.repository.ts @@ -78,6 +78,19 @@ export class NotificationsRepository { .exec(); } + async countUnreadByFilter( + recipientId: string, + filter: FilterQuery = {}, + ): Promise { + return this.notificationModel + .countDocuments({ + recipientId: new Types.ObjectId(recipientId), + read: false, + ...filter, + }) + .exec(); + } + async count(filter: FilterQuery): Promise { return this.notificationModel.countDocuments(filter).exec(); } diff --git a/src/modules/notifications/notifications.service.spec.ts b/src/modules/notifications/notifications.service.spec.ts index 22a546a..2e21a7f 100644 --- a/src/modules/notifications/notifications.service.spec.ts +++ b/src/modules/notifications/notifications.service.spec.ts @@ -1,7 +1,19 @@ import { NotFoundException } from '@nestjs/common'; +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; +import { NotificationUnreadCountQueryDto } from './dto/notification-query.dto'; import { NotificationsService } from './notifications.service'; describe('NotificationsService', () => { + it('rejects invalid unread-count category through DTO validation', async () => { + const dto = plainToInstance(NotificationUnreadCountQueryDto, { category: 'badges' }); + + const errors = await validate(dto); + + expect(errors.length).toBeGreaterThan(0); + expect(errors[0].property).toBe('category'); + }); + it('creates mention notifications with mention type', async () => { const notificationsRepository = { create: jest.fn().mockResolvedValue({ toJSON: () => ({ _id: 'notification-1' }) }), @@ -107,6 +119,139 @@ describe('NotificationsService', () => { expect(notificationsRepository.countUnread).toHaveBeenCalledWith('507f1f77bcf86cd799439011'); }); + it('returns total unread count without category for backward compatibility', async () => { + const notificationsRepository = { + countUnread: jest.fn().mockResolvedValue(10), + }; + const notificationsGateway = {}; + + const service = new NotificationsService( + notificationsRepository as any, + notificationsGateway as any, + ); + + await expect(service.getUnreadCountByCategory('507f1f77bcf86cd799439011')).resolves.toEqual({ + unreadCount: 10, + }); + expect(notificationsRepository.countUnread).toHaveBeenCalledWith('507f1f77bcf86cd799439011'); + }); + + it('returns unread count for interactions category only', async () => { + const notificationsRepository = { + countUnreadByFilter: jest.fn().mockResolvedValue(4), + }; + const notificationsGateway = {}; + + const service = new NotificationsService( + notificationsRepository as any, + notificationsGateway as any, + ); + + await expect( + service.getUnreadCountByCategory('507f1f77bcf86cd799439011', 'interactions'), + ).resolves.toEqual({ + unreadCount: 4, + category: 'interactions', + }); + expect(notificationsRepository.countUnreadByFilter).toHaveBeenCalledWith( + '507f1f77bcf86cd799439011', + { + type: { + $in: [ + 'like', + 'comment', + 'reply', + 'mention', + 'save', + 'share', + 'collaboration_request', + 'system', + ], + }, + }, + ); + }); + + it('returns unread count for messages category only', async () => { + const notificationsRepository = { + countUnreadByFilter: jest.fn().mockResolvedValue(3), + }; + const notificationsGateway = {}; + + const service = new NotificationsService( + notificationsRepository as any, + notificationsGateway as any, + ); + + await expect( + service.getUnreadCountByCategory('507f1f77bcf86cd799439011', 'messages'), + ).resolves.toEqual({ + unreadCount: 3, + category: 'messages', + }); + expect(notificationsRepository.countUnreadByFilter).toHaveBeenCalledWith( + '507f1f77bcf86cd799439011', + { type: { $in: ['message'] } }, + ); + }); + + it('returns unread count for follow requests category only', async () => { + const notificationsRepository = { + countUnreadByFilter: jest.fn().mockResolvedValue(2), + }; + const notificationsGateway = {}; + + const service = new NotificationsService( + notificationsRepository as any, + notificationsGateway as any, + ); + + await expect( + service.getUnreadCountByCategory('507f1f77bcf86cd799439011', 'follow_requests'), + ).resolves.toEqual({ + unreadCount: 2, + category: 'follow_requests', + }); + expect(notificationsRepository.countUnreadByFilter).toHaveBeenCalledWith( + '507f1f77bcf86cd799439011', + { + type: { + $in: ['follow_request', 'follow_request_approved', 'follow_request_rejected'], + }, + }, + ); + }); + + it('returns grouped unread counts for badge hydration', async () => { + const notificationsRepository = { + countUnread: jest.fn().mockResolvedValue(10), + countUnreadByFilter: jest + .fn() + .mockResolvedValueOnce(4) + .mockResolvedValueOnce(3) + .mockResolvedValueOnce(1) + .mockResolvedValueOnce(2) + .mockResolvedValueOnce(1) + .mockResolvedValueOnce(0), + }; + const notificationsGateway = {}; + + const service = new NotificationsService( + notificationsRepository as any, + notificationsGateway as any, + ); + + await expect(service.getUnreadCounts('507f1f77bcf86cd799439011')).resolves.toEqual({ + total: 10, + interactions: 4, + messages: 3, + follows: 1, + followRequests: 2, + collaboration: 1, + system: 0, + }); + }); + it('filters notifications by interactions category', async () => { const notificationsRepository = { findMine: jest.fn().mockResolvedValue([]), diff --git a/src/modules/notifications/notifications.service.ts b/src/modules/notifications/notifications.service.ts index 7bf88ff..0d7dbfe 100644 --- a/src/modules/notifications/notifications.service.ts +++ b/src/modules/notifications/notifications.service.ts @@ -22,6 +22,8 @@ const NOTIFICATION_CATEGORY_TYPES: Record