diff --git a/postman/Oudelaa-Auth-Users-Posts.postman_collection.json b/postman/Oudelaa-Auth-Users-Posts.postman_collection.json index 3497eed..fe68c7a 100644 --- a/postman/Oudelaa-Auth-Users-Posts.postman_collection.json +++ b/postman/Oudelaa-Auth-Users-Posts.postman_collection.json @@ -756,6 +756,196 @@ } ] }, + { + "name": "Search", + "item": [ + { + "name": "Global Search - All", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search?q={{searchQuery}}&type={{searchType}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('query');", + "pm.expect(json).to.have.property('users');", + "pm.expect(json).to.have.property('posts');", + "pm.expect(json).to.have.property('hashtags');", + "pm.expect(json.users.pagination).to.exist;", + "pm.expect(json.posts.pagination).to.exist;", + "pm.expect(json.hashtags.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Global Search - Users Type", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search?q={{searchQuery}}&type=users&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.users).to.have.property('items');", + "pm.expect(json.users).to.have.property('pagination');" + ] + } + } + ] + }, + { + "name": "Search Users", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/users?q={{searchQuery}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " const target = json.items[0];", + " pm.expect(target).to.have.property('username');", + " pm.expect(target).to.have.property('isFollowing');", + " pm.environment.set('targetUserId', target._id || target.id);", + "}" + ] + } + } + ] + }, + { + "name": "Search Posts", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/posts?q={{searchQuery}}&page=1&limit={{searchLimit}}&sortOrder={{listSortOrder}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " const post = json.items[0];", + " pm.expect(post).to.have.property('media');", + " pm.expect(post).to.have.property('isLiked');", + " pm.expect(post).to.have.property('isSaved');", + " pm.environment.set('postId', post._id || post.id);", + "}" + ] + } + } + ] + }, + { + "name": "Search Hashtags", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/hashtags?q={{searchQuery}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " pm.expect(json.items[0]).to.have.property('tag');", + " pm.expect(json.items[0]).to.have.property('postsCount');", + " pm.environment.set('postHashtag', json.items[0].tag);", + "}" + ] + } + } + ] + }, + { + "name": "Search Suggestions", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/suggestions?q={{searchQuery}}&limit={{searchSuggestionLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('users');", + "pm.expect(json).to.have.property('hashtags');" + ] + } + } + ] + } + ] + }, { "name": "Users", "item": [ @@ -3083,6 +3273,139 @@ } ] }, + { + "name": "Collaboration Requests", + "item": [ + { + "name": "Create Collaboration Request", + "request": { + "method": "POST", + "url": "{{baseUrl}}/posts/{{postId}}/collaboration-requests", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"targetUserId\": \"{{targetUserId}}\",\n \"collaborationType\": \"{{collaborationType}}\",\n \"message\": \"{{collaborationMessage}}\",\n \"attachmentUrl\": \"{{collaborationAttachmentUrl}}\",\n \"attachmentType\": \"{{collaborationAttachmentType}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200 or 201', function () { pm.expect([200, 201]).to.include(pm.response.code); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('request');", + "pm.environment.set('collaborationRequestId', json.request._id || json.request.id);", + "pm.test('Rich collaboration fields are accepted', function () {", + " pm.expect(json.request).to.have.property('collaborationType');", + " pm.expect(json.request).to.have.property('message');", + " pm.expect(json.request).to.have.property('attachmentUrl');", + " pm.expect(json.request).to.have.property('attachmentType');", + "});" + ] + } + } + ] + }, + { + "name": "List My Collaboration Requests", + "request": { + "method": "GET", + "url": "{{baseUrl}}/collaboration-requests?page=1&limit=20", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);", + " pm.expect(json.items[0]).to.have.property('status');", + " pm.expect(json.items[0]).to.have.property('collaborationType');", + "}" + ] + } + } + ] + }, + { + "name": "Approve Collaboration Request", + "request": { + "method": "PATCH", + "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/approve", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('approved', true);", + "pm.expect(json).to.have.property('request');" + ] + } + } + ] + }, + { + "name": "Reject Collaboration Request", + "request": { + "method": "PATCH", + "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/reject", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('rejected', true);", + "pm.expect(json).to.have.property('request');" + ] + } + } + ] + } + ] + }, { "name": "Comments", "item": [ @@ -8667,6 +8990,38 @@ { "key": "auditAction", "value": "" + }, + { + "key": "searchQuery", + "value": "music" + }, + { + "key": "searchType", + "value": "all" + }, + { + "key": "searchLimit", + "value": "20" + }, + { + "key": "searchSuggestionLimit", + "value": "5" + }, + { + "key": "collaborationType", + "value": "duet" + }, + { + "key": "collaborationMessage", + "value": "I want to collaborate on this piece" + }, + { + "key": "collaborationAttachmentUrl", + "value": "" + }, + { + "key": "collaborationAttachmentType", + "value": "audio" } ] } diff --git a/postman/Oudelaa-Dashboard.postman_collection.json b/postman/Oudelaa-Dashboard.postman_collection.json index b29ccef..6f1baad 100644 --- a/postman/Oudelaa-Dashboard.postman_collection.json +++ b/postman/Oudelaa-Dashboard.postman_collection.json @@ -1,4326 +1,4645 @@ { - "info": { - "name": "Oudelaa Dashboard API", - "description": "Dashboard/admin collection generated from Oudelaa master collection.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "Auth", - "item": [ - { - "name": "Login", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.user).to.exist;", - "const uid = json.user._id || json.user.id;", - "pm.environment.set(\u0027accessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027refreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027userId\u0027, uid);", - "pm.environment.set(\u0027currentUserId\u0027, uid);" - ] - } - } - ] - }, - { - "name": "Target User Login", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{targetLoginEmail}}\",\n \"password\": \"{{targetLoginPassword}}\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.user).to.exist;", - "const uid = json.user._id || json.user.id;", - "pm.environment.set(\u0027targetAccessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027targetRefreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027targetUserId\u0027, uid);" - ] - } - } - ] - }, - { - "name": "Admin Login", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{adminEmail}}\",\n \"password\": \"{{adminPassword}}\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027adminAccessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027adminRefreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027adminUserId\u0027, (json.user \u0026\u0026 (json.user._id || json.user.id)) || pm.environment.get(\u0027adminUserId\u0027));" - ] - } - } - ] - }, - { - "name": "Refresh", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/refresh", - "body": { - "mode": "raw", - "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.user).to.exist;", - "pm.environment.set(\u0027accessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027refreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027userId\u0027, json.user._id || json.user.id);" - ] - } - } - ] - }, - { - "name": "Logout", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/logout", - "body": { - "mode": "raw", - "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.eql(\u0027Logged out successfully\u0027);" - ] - } - } - ] - }, - { - "name": "SuperAdmin", - "item": [ - { - "name": "SuperAdmin Login", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/superadmin/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"admin@oudelaa.com\",\n \"password\": \"SuperAdminStrongPass123!\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.accessToken).to.exist;", - "pm.expect(json.refreshToken).to.exist;", - "pm.environment.set(\u0027superAdminAccessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027superAdminRefreshToken\u0027, json.refreshToken);" - ] - } - } - ] - }, - { - "name": "SuperAdmin Refresh", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/superadmin/refresh", - "body": { - "mode": "raw", - "raw": "{\n \"refreshToken\": \"{{superAdminRefreshToken}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.accessToken).to.exist;", - "pm.expect(json.refreshToken).to.exist;", - "pm.environment.set(\u0027superAdminAccessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027superAdminRefreshToken\u0027, json.refreshToken);" - ] - } - } - ] - }, - { - "name": "SuperAdmin Logout", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/superadmin/logout", - "body": { - "mode": "raw", - "raw": "{\n \"refreshToken\": \"{{superAdminRefreshToken}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.eql(\u0027Superadmin logged out successfully\u0027);" - ] - } - } - ] - } - ] - }, - { - "name": "SuperAdmin List Sessions", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/auth/superadmin/sessions", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "auth", - "superadmin", - "sessions" - ] - } - } - }, - { - "name": "SuperAdmin Revoke Session", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/auth/superadmin/sessions/{{sessionId}}/revoke", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "auth", - "superadmin", - "sessions", - "{{sessionId}}", - "revoke" - ] - } - } - } - ] - }, - { - "name": "Media", - "item": [ - { - "name": "Media Health", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/media/health" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.storage).to.be.an(\u0027object\u0027);", - "pm.expect(json.storage.provider).to.be.oneOf([\u0027local\u0027, \u0027s3\u0027]);", - "pm.expect(json.serving.rangeRequests).to.eql(true);", - "pm.expect(json.processing).to.be.an(\u0027object\u0027);" - ] - } - } - ] - } - ] - }, - { - "name": "Users", - "item": [ - { - "name": "SuperAdmin List Admins", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/admins?page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027adminUserId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "SuperAdmin Update Admin", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/admins/{{adminUserId}}", - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Store Admin Updated\",\n \"stageName\": \"Music Store Owner\",\n \"bio\": \"Admin managing marketplace tools\",\n \"location\": \"Riyadh, Saudi Arabia\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027role\u0027);", - "pm.expect(json.role).to.eql(\u0027admin\u0027);" - ] - } - } - ] - }, - { - "name": "SuperAdmin Delete Admin", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/admins/{{adminUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.eql(\u0027Admin deleted successfully\u0027);" - ] - } - } - ] - }, - { - "name": "SuperAdmin Create Admin", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/create-admin", - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Store Admin\",\n \"username\": \"{{adminUsername}}\",\n \"email\": \"{{adminEmail}}\",\n \"password\": \"{{adminPassword}}\",\n \"confirmPassword\": \"{{adminPassword}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027adminUserId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "SuperAdmin Set User Role", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/{{adminUserId}}/role", - "body": { - "mode": "raw", - "raw": "{\n \"role\": \"admin\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.role).to.eql(\u0027admin\u0027);" - ] - } - } - ] - }, - { - "name": "Admin Get Users", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin?page=1\u0026limit=10" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027adminUserId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Admin Get User By Id", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/{{adminUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json._id || json.id).to.exist;" - ] - } - } - ] - }, - { - "name": "Admin Update User", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/{{adminUserId}}", - "body": { - "mode": "raw", - "raw": "{\n \"stageName\": \"Updated by SuperAdmin\",\n \"bio\": \"Profile updated by admin\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.stageName).to.eql(\u0027Updated by SuperAdmin\u0027);" - ] - } - } - ] - }, - { - "name": "Admin Disable User", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/{{adminUserId}}/disable", - "body": { - "mode": "raw", - "raw": "{\n \"reason\": \"Violation of community guidelines\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.isDisabled).to.eql(true);" - ] - } - } - ] - }, - { - "name": "Admin Enable User", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/{{adminUserId}}/enable" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.isDisabled).to.eql(false);" - ] - } - } - ] - }, - { - "name": "Admin Delete User", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/users/admin/{{adminUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.eql(\u0027User deleted successfully\u0027);" - ] - } - } - ] - }, - { - "name": "Admin Discover Users", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/users/admin/discover?page=1\u0026limit=20\u0026q={{usersQuery}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - "admin", - "discover" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - }, - { - "key": "q", - "value": "{{usersQuery}}" - } - ] - } - } - }, - { - "name": "Admin Get User Profile Overview", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/users/admin/{{id}}/profile-overview", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - "admin", - "{{id}}", - "profile-overview" - ] - } - } - } - ] - }, - { - "name": "Posts", - "item": [ - { - "name": "Admin Get Posts Moderation", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/admin/moderation?page=1\u0026limit=20\u0026moderationStatus=flagged", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "admin", - "moderation" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - }, - { - "key": "moderationStatus", - "value": "flagged" - } - ] - } - } - } - ] - }, - { - "name": "Comments", - "item": [ - { - "name": "Admin Delete Comment", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/comments/admin/{{commentId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);" - ] - } - } - ] - }, - { - "name": "Admin List Comments", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/comments/admin?page=1\u0026limit=20\u0026moderationStatus=flagged", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "comments", - "admin" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - }, - { - "key": "moderationStatus", - "value": "flagged" - } - ] - } - } - } - ] - }, - { - "name": "Notifications", - "item": [ - { - "name": "SuperAdmin Notifications", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/notifications/superadmin?page=1\u0026limit=20\u0026type={{notificationType}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "notifications", - "superadmin" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - }, - { - "key": "type", - "value": "{{notificationType}}" - } - ] - } - } - } - ] - }, - { - "name": "Smoke", - "item": [ - { - "name": "Login (Smoke)", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "const uid = json.user._id || json.user.id;", - "pm.environment.set(\u0027accessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027refreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027userId\u0027, uid);" - ] - } - } - ] - } - ] - }, - { - "name": "Cleanup", - "item": [ - { - "name": "Delete Instrument (if exists)", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/instruments/{{instrumentId}}" - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027instrumentId\u0027)) { postman.setNextRequest(null); }" - ] - } - } - ] - } - ] - }, - { - "name": "Marketplace", - "item": [ - { - "name": "02 Shop Profile", - "item": [ - { - "name": "Admin", - "item": [ - { - "name": "Admin Update Shop Profile", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/shop-profile", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "shopName", - "value": "Awtarna Store", - "type": "text" - }, - { - "key": "shopDescription", - "value": "Trusted marketplace shop profile", - "type": "text" - }, - { - "key": "shopLocation", - "value": "Riyadh", - "type": "text" - }, - { - "key": "shopLatitude", - "value": "24.7136", - "type": "text" - }, - { - "key": "shopLongitude", - "value": "46.6753", - "type": "text" - }, - { - "key": "shopImageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "shopImageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027adminAccessToken\u0027)) { throw new Error(\u0027Missing environment var: adminAccessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.shopName).to.eql(\u0027Oudelaa Music Store\u0027);" - ] - } - } - ] - }, - { - "name": "Admin Get My Shop Profile", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/shop-profile/me" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027shopName\u0027);" - ] - } - } - ] - } - ] - }, - { - "name": "Public", - "item": [ - { - "name": "Public Get Shop By Admin Id", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/marketplace/shops/{{adminUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027shopName\u0027);" - ] - } - } - ] - } - ] - } - ] - }, - { - "name": "03 Repair Shops", - "item": [ - { - "name": "Admin", - "item": [ - { - "name": "Admin Create Repair Shop (One Shop Per Admin)", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/repair-shops", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "name", - "value": "Fix Strings Workshop", - "type": "text" - }, - { - "key": "description", - "value": "Repair shop for oud and violin", - "type": "text" - }, - { - "key": "services", - "value": "repair,setup,cleaning", - "type": "text" - }, - { - "key": "phone", - "value": "+966500000000", - "type": "text" - }, - { - "key": "whatsapp", - "value": "+966500000000", - "type": "text" - }, - { - "key": "location", - "value": "Riyadh", - "type": "text" - }, - { - "key": "latitude", - "value": "24.7136", - "type": "text" - }, - { - "key": "longitude", - "value": "46.6753", - "type": "text" - }, - { - "key": "isActive", - "value": "true", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027adminAccessToken\u0027)) { throw new Error(\u0027Missing environment var: adminAccessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027repairShopId\u0027, json._id || json.id);", - "pm.expect(json.name).to.eql(\u0027Oudelaa Repair Center\u0027);" - ] - } - } - ] - }, - { - "name": "Admin Create Second Repair Shop Should Fail", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/repair-shops", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "name", - "value": "Fix Strings Workshop", - "type": "text" - }, - { - "key": "description", - "value": "Repair shop for oud and violin", - "type": "text" - }, - { - "key": "services", - "value": "repair,setup,cleaning", - "type": "text" - }, - { - "key": "phone", - "value": "+966500000000", - "type": "text" - }, - { - "key": "whatsapp", - "value": "+966500000000", - "type": "text" - }, - { - "key": "location", - "value": "Riyadh", - "type": "text" - }, - { - "key": "latitude", - "value": "24.7136", - "type": "text" - }, - { - "key": "longitude", - "value": "46.6753", - "type": "text" - }, - { - "key": "isActive", - "value": "true", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027adminAccessToken\u0027)) { throw new Error(\u0027Missing environment var: adminAccessToken\u0027); }", - "if (!pm.environment.get(\u0027repairShopId\u0027)) { throw new Error(\u0027Create the first repair shop before running this negative test.\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 400\u0027, function () { pm.response.to.have.status(400); });", - "const json = pm.response.json();", - "const messages = Array.isArray(json.message) ? json.message : [json.message];", - "pm.expect(messages.join(\u0027 \u0027)).to.include(\u0027Each admin can create one marketplace shop only\u0027);" - ] - } - } - ] - }, - { - "name": "Admin Update Repair Shop", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/repair-shops/{{repairShopId}}", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "name", - "value": "Updated Fix Strings Workshop", - "type": "text" - }, - { - "key": "description", - "value": "Updated repair shop description", - "type": "text" - }, - { - "key": "services", - "value": "repair,setup,polishing", - "type": "text" - }, - { - "key": "phone", - "value": "+966511111111", - "type": "text" - }, - { - "key": "whatsapp", - "value": "+966511111111", - "type": "text" - }, - { - "key": "location", - "value": "Jeddah", - "type": "text" - }, - { - "key": "latitude", - "value": "21.5433", - "type": "text" - }, - { - "key": "longitude", - "value": "39.1728", - "type": "text" - }, - { - "key": "isActive", - "value": "true", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Admin Get My Repair Shops", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/repair-shops/me?q={{repairShopQuery}}\u0026isActive={{repairShopIsActive}}\u0026sortBy={{repairShopSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027repairShopId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Admin Delete Repair Shop", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/repair-shops/{{repairShopId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);" - ] - } - } - ] - } - ] - } - ] - }, - { - "name": "04 Musical Instruments", - "item": [ - { - "name": "Admin", - "item": [ - { - "name": "Admin Create Instrument", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/instruments", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "title", - "value": "Concert Guitar", - "type": "text" - }, - { - "key": "description", - "value": "Acoustic guitar in excellent condition", - "type": "text" - }, - { - "key": "price", - "value": "2100", - "type": "text" - }, - { - "key": "currency", - "value": "SAR", - "type": "text" - }, - { - "key": "quantity", - "value": "1", - "type": "text" - }, - { - "key": "condition", - "value": "used", - "type": "text" - }, - { - "key": "instrumentType", - "value": "Guitar", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027adminAccessToken\u0027)) { throw new Error(\u0027Missing environment var: adminAccessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027instrumentId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "Admin Update Instrument", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/instruments/{{instrumentId}}", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "title", - "value": "Updated Concert Guitar", - "type": "text" - }, - { - "key": "description", - "value": "Updated instrument description", - "type": "text" - }, - { - "key": "price", - "value": "2400", - "type": "text" - }, - { - "key": "currency", - "value": "SAR", - "type": "text" - }, - { - "key": "quantity", - "value": "1", - "type": "text" - }, - { - "key": "condition", - "value": "used", - "type": "text" - }, - { - "key": "instrumentType", - "value": "Violin", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Admin Get My Instruments", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/instruments/me?q={{marketplaceQuery}}\u0026minPrice={{marketplaceMinPrice}}\u0026maxPrice={{marketplaceMaxPrice}}\u0026isActive={{marketplaceIsActive}}\u0026condition={{marketplaceCondition}}\u0026instrumentType={{marketplaceInstrumentType}}\u0026sortBy={{marketplaceSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027instrumentId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Admin Delete Instrument", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/instruments/{{instrumentId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);" - ] - } - } - ] - } - ] - } - ] - }, - { - "name": "05 General Marketplace", - "item": [ - { - "name": "Admin", - "item": [ - { - "name": "Admin Create Listing", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/listings", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "title", - "value": "Microphone SM58", - "type": "text" - }, - { - "key": "description", - "value": "Durable dynamic microphone for studio and stage", - "type": "text" - }, - { - "key": "price", - "value": "450", - "type": "text" - }, - { - "key": "currency", - "value": "SAR", - "type": "text" - }, - { - "key": "quantity", - "value": "1", - "type": "text" - }, - { - "key": "condition", - "value": "used", - "type": "text" - }, - { - "key": "instrumentType", - "value": "Microphone", - "type": "text" - }, - { - "key": "listingCategory", - "value": "audio_gear", - "type": "text" - }, - { - "key": "isActive", - "value": "true", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027adminAccessToken\u0027)) { throw new Error(\u0027Missing environment var: adminAccessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027listingId\u0027, json._id || json.id);", - "pm.expect(json.listingCategory).to.eql(\u0027accessory\u0027);" - ] - } - } - ] - }, - { - "name": "Admin Update Listing", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/listings/{{listingId}}", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "title", - "value": "Updated Microphone SM58", - "type": "text" - }, - { - "key": "description", - "value": "Updated marketplace listing description", - "type": "text" - }, - { - "key": "price", - "value": "500", - "type": "text" - }, - { - "key": "currency", - "value": "SAR", - "type": "text" - }, - { - "key": "quantity", - "value": "1", - "type": "text" - }, - { - "key": "condition", - "value": "used", - "type": "text" - }, - { - "key": "instrumentType", - "value": "Microphone", - "type": "text" - }, - { - "key": "listingCategory", - "value": "audio_gear", - "type": "text" - }, - { - "key": "isActive", - "value": "true", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Admin Get My Listings", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/listings/me?q={{marketplaceQuery}}\u0026minPrice={{marketplaceMinPrice}}\u0026maxPrice={{marketplaceMaxPrice}}\u0026isActive={{marketplaceIsActive}}\u0026listingCategory={{marketplaceListingCategory}}\u0026condition={{marketplaceCondition}}\u0026instrumentType={{marketplaceInstrumentType}}\u0026sortBy={{marketplaceSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027listingId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Admin Delete Listing", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{adminAccessToken}}" - } - ], - "url": "{{baseUrl}}/marketplace/admin/listings/{{listingId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);" - ] - } - } - ] - } - ] - } - ] - }, - { - "name": "SuperAdmin List Marketplace Listings", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/listings?page=1\u0026limit=20", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "listings" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - } - ] - } - } - }, - { - "name": "SuperAdmin Update Listing Status", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/listings/{{id}}/status", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "listings", - "{{id}}", - "status" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"isActive\": false,\n \"moderationStatus\": \"flagged\",\n \"moderationReason\": \"Needs marketplace review\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "SuperAdmin Delete Listing", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/listings/{{id}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "listings", - "{{id}}" - ] - } - } - }, - { - "name": "SuperAdmin List Repair Shops", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/repair-shops?page=1\u0026limit=20", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "repair-shops" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - } - ] - } - } - }, - { - "name": "SuperAdmin Update Repair Shop Status", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/repair-shops/{{id}}/status", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "repair-shops", - "{{id}}", - "status" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"isActive\": false,\n \"moderationStatus\": \"flagged\",\n \"moderationReason\": \"Needs marketplace review\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "SuperAdmin Delete Repair Shop", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/repair-shops/{{id}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "repair-shops", - "{{id}}" - ] - } - } - }, - { - "name": "SuperAdmin Create Listing For Admin", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/admins/{{adminId}}/listings", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "admins", - "{{adminId}}", - "listings" - ] - }, - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "title", - "value": "Superadmin listing", - "type": "text" - }, - { - "key": "description", - "value": "Created by superadmin", - "type": "text" - }, - { - "key": "price", - "value": "1000", - "type": "text" - }, - { - "key": "currency", - "value": "SAR", - "type": "text" - }, - { - "key": "quantity", - "value": "1", - "type": "text" - }, - { - "key": "listingCategory", - "value": "general", - "type": "text" - } - ] - } - } - }, - { - "name": "SuperAdmin Create Instrument For Admin", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/admins/{{adminId}}/instruments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "admins", - "{{adminId}}", - "instruments" - ] - }, - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "title", - "value": "Superadmin instrument", - "type": "text" - }, - { - "key": "description", - "value": "Created by superadmin", - "type": "text" - }, - { - "key": "price", - "value": "1200", - "type": "text" - }, - { - "key": "currency", - "value": "SAR", - "type": "text" - }, - { - "key": "quantity", - "value": "1", - "type": "text" - }, - { - "key": "instrumentType", - "value": "Oud", - "type": "text" - } - ] - } - } - }, - { - "name": "SuperAdmin Create Repair Shop For Admin", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/admins/{{adminId}}/repair-shops", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "admins", - "{{adminId}}", - "repair-shops" - ] - }, - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "name", - "value": "Superadmin Repair Shop", - "type": "text" - }, - { - "key": "description", - "value": "Created by superadmin", - "type": "text" - }, - { - "key": "services", - "value": "oud repair,string replacement", - "type": "text" - }, - { - "key": "location", - "value": "Riyadh", - "type": "text" - } - ] - } - } - }, - { - "name": "SuperAdmin Update Admin Shop Profile", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/marketplace/superadmin/admins/{{adminId}}/shop-profile", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "marketplace", - "superadmin", - "admins", - "{{adminId}}", - "shop-profile" - ] - }, - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "shopName", - "value": "Superadmin Shop", - "type": "text" - }, - { - "key": "shopDescription", - "value": "Updated by superadmin", - "type": "text" - }, - { - "key": "shopLocation", - "value": "Riyadh", - "type": "text" - } - ] - } - } - } - ] - }, - { - "name": "Reports", - "item": [ - { - "name": "Superadmin Reports", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": "{{baseUrl}}/reports/superadmin?status=open" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Superadmin Update Report Status", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/reports/superadmin/{{reportId}}/status", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "reports", - "superadmin", - "{{reportId}}", - "status" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"status\": \"in_review\",\n \"resolutionNote\": \"Under review\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - } - ] - }, - { - "name": "Audit", - "item": [ - { - "name": "SuperAdmin Audit Logs", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/audit/superadmin/logs?page=1\u0026limit=20\u0026action={{auditAction}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "audit", - "superadmin", - "logs" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - }, - { - "key": "action", - "value": "{{auditAction}}" - } - ] - } - } - } - ] - }, - { - "name": "SuperAdmin", - "item": [ - { - "name": "Get SuperAdmin Session", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/session", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "session" - ] - } - } - }, - { - "name": "Get SuperAdmin Overview", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/overview", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "overview" - ] - } - } - }, - { - "name": "Get SuperAdmin Charts", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/charts?range=30d", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "charts" - ], - "query": [ - { - "key": "range", - "value": "30d" - } - ] - } - } - }, - { - "name": "Get SuperAdmin Recent Activity", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/recent-activity?page=1\u0026limit=20", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "recent-activity" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - } - ] - } - } - }, - { - "name": "Get SuperAdmin Reports", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/reports?range=30d", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "reports" - ], - "query": [ - { - "key": "range", - "value": "30d" - } - ] - } - } - }, - { - "name": "Get SuperAdmin Ops", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/ops", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "ops" - ] - } - } - }, - { - "name": "List SuperAdmin Cases", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/cases?page=1\u0026limit=20\u0026status=open", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "cases" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - }, - { - "key": "status", - "value": "open" - } - ] - } - } - }, - { - "name": "Create SuperAdmin Case", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/cases", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "cases" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"title\": \"Manual review\",\n \"description\": \"Created from Postman\",\n \"targetType\": \"post\",\n \"targetId\": \"{{postId}}\",\n \"priority\": \"medium\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Update SuperAdmin Case", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/cases/{{caseId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "cases", - "{{caseId}}" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"status\": \"in_review\",\n \"resolutionNote\": \"Updated from Postman\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Perform SuperAdmin Bulk Action", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/bulk-actions", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "bulk-actions" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"targetType\": \"post\",\n \"targetIds\": [\n \"{{bulkActionTargetId}}\"\n ],\n \"action\": \"flag\",\n \"reason\": \"Bulk moderation from Postman\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Get SuperAdmin Settings", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/settings", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "settings" - ] - } - } - }, - { - "name": "Get SuperAdmin Settings History", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/settings/history?page=1\u0026limit=20", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "settings", - "history" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - } - ] - } - } - }, - { - "name": "Update SuperAdmin Settings", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/settings", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "settings" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"maintenanceMode\": false\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Restore SuperAdmin Settings Version", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/settings/history/{{historyId}}/restore", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "settings", - "history", - "{{historyId}}", - "restore" - ] - } - } - }, - { - "name": "Update Post Moderation Status", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/posts/{{postId}}/status", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "posts", - "{{postId}}", - "status" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"status\": \"flagged\",\n \"reason\": \"Needs review\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Delete Post By SuperAdmin", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/posts/{{postId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "posts", - "{{postId}}" - ] - } - } - }, - { - "name": "Restore Post By SuperAdmin", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/posts/{{postId}}/restore", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "posts", - "{{postId}}", - "restore" - ] - } - } - }, - { - "name": "Update Comment Moderation Status", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/comments/{{commentId}}/status", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "comments", - "{{commentId}}", - "status" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"status\": \"flagged\",\n \"reason\": \"Needs review\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Delete Comment By SuperAdmin", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/comments/{{commentId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "comments", - "{{commentId}}" - ] - } - } - }, - { - "name": "Restore Comment By SuperAdmin", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/comments/{{commentId}}/restore", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "comments", - "{{commentId}}", - "restore" - ] - } - } - }, - { - "name": "Update User Status By SuperAdmin", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{superAdminAccessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/superadmin/users/{{userId}}/status", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "superadmin", - "users", - "{{userId}}", - "status" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"isDisabled\": true,\n \"reason\": \"Policy violation\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - } - ] - }, - { - "name": "oudelaa_dashboard", - "item": [ - { - "name": "Follow System", - "item": [ - { - "name": "Follow User", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{targetUserId}}/follow", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Follow response\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027isFollowing\u0027); pm.expect(json).to.have.property(\u0027targetUserId\u0027); });" - ] - } - } - }, - { - "name": "Unfollow User", - "request": { - "method": "DELETE", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{targetUserId}}/follow", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Unfollow response\u0027, function () { const json = pm.response.json(); pm.expect(json.isFollowing).to.eql(false); });" - ] - } - } - }, - { - "name": "Get Follow Status", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{targetUserId}}/follow-status", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Follow status shape\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027isFollowing\u0027); pm.expect(json).to.have.property(\u0027isFollowedBy\u0027); pm.expect(json).to.have.property(\u0027isMutual\u0027); });" - ] - } - } - }, - { - "name": "Get User Followers", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/followers?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Get User Following", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/following?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Get My Followers", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/me/followers?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Get My Following", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/me/following?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Legacy Toggle Follow", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/follows/toggle", - "body": { - "mode": "raw", - "raw": "{\n \"targetUserId\": \"{{targetUserId}}\"\n}" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "List Follow Requests", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/follows/requests?page=1\u0026limit=20", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Approve Follow Request", - "request": { - "method": "PATCH", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/follows/requests/{{followRequestId}}/approve", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Reject Follow Request", - "request": { - "method": "PATCH", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/follows/requests/{{followRequestId}}/reject", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - } - ] - }, - { - "name": "Presence \u0026 Profile", - "item": [ - { - "name": "Get User Presence", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/presence", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Presence shape\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027userId\u0027); pm.expect(json).to.have.property(\u0027isOnline\u0027); pm.expect(json).to.have.property(\u0027lastSeenAt\u0027); });" - ] - } - } - }, - { - "name": "Get User Profile With isFollowing", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Get Profile Overview With Presence/Follow State", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/profile-overview", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - } - ] - }, - { - "name": "User Posts", - "item": [ - { - "name": "Get Posts By User Id", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/posts?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Paginated posts\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027items\u0027); pm.expect(json).to.have.property(\u0027pagination\u0027); });" - ] - } - } - }, - { - "name": "Legacy Get Posts By User Id", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/posts/user/{{userId}}?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - } - ] - }, - { - "name": "Devices FCM", - "item": [ - { - "name": "Register Device FCM Token", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/devices/register", - "body": { - "mode": "raw", - "raw": "{\n \"fcmToken\": \"{{fcmToken}}\",\n \"platform\": \"android\",\n \"deviceId\": \"{{deviceId}}\",\n \"appVersion\": \"1.0.0\",\n \"locale\": \"ar\"\n}" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Device registered\u0027, function () { const json = pm.response.json(); pm.expect(json.message).to.eql(\u0027Device registered successfully\u0027); pm.expect(json).to.have.property(\u0027device\u0027); });" - ] - } - } - }, - { - "name": "Unregister Device FCM Token", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/devices/unregister", - "body": { - "mode": "raw", - "raw": "{\n \"fcmToken\": \"{{fcmToken}}\"\n}" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Device unregistered\u0027, function () { const json = pm.response.json(); pm.expect(json.message).to.eql(\u0027Device unregistered successfully\u0027); });" - ] - } - } - } - ] - }, - { - "name": "Notifications", - "item": [ - { - "name": "Get My Notifications", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/notifications?read={{notificationRead}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20\u0026type={{notificationType}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Get My Unread Notifications Count", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/notifications/unread-count", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Mark Notification Read", - "request": { - "method": "PATCH", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/notifications/{{notificationId}}/read", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Mark All Notifications Read", - "request": { - "method": "PATCH", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/notifications/read-all", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "SuperAdmin Notifications (Dashboard Only)", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/notifications/superadmin?page=1\u0026limit=20\u0026type={{notificationType}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - } - ] - }, - { - "name": "Collaboration Requests", - "item": [ - { - "name": "Create Collaboration Request", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/posts/{{postId}}/collaboration-requests", - "body": { - "mode": "raw", - "raw": "{\n \"targetUserId\": \"{{targetUserId}}\"\n}" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Collaboration request response\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027request\u0027); });" - ] - } - } - }, - { - "name": "List My Collaboration Requests", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/collaboration-requests?page=1\u0026limit=20", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Approve Collaboration Request", - "request": { - "method": "PATCH", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/approve", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Approved\u0027, function () { const json = pm.response.json(); pm.expect(json.approved).to.eql(true); });" - ] - } - } - }, - { - "name": "Reject Collaboration Request", - "request": { - "method": "PATCH", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/reject", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Rejected\u0027, function () { const json = pm.response.json(); pm.expect(json.rejected).to.eql(true); });" - ] - } - } - } - ] - } - ] - } - ], - "variable": [ - { - "key": "baseUrl", - "value": "http://localhost:4000/api/v1", - "type": "string" - }, - { - "key": "accessToken", - "value": "", - "type": "string" - }, - { - "key": "refreshToken", - "value": "", - "type": "string" - }, - { - "key": "superAdminAccessToken", - "value": "", - "type": "string" - }, - { - "key": "superAdminRefreshToken", - "value": "", - "type": "string" - }, - { - "key": "targetAccessToken", - "value": "", - "type": "string" - }, - { - "key": "targetRefreshToken", - "value": "", - "type": "string" - }, - { - "key": "userId", - "value": "", - "type": "string" - }, - { - "key": "currentUserId", - "value": "", - "type": "string" - }, - { - "key": "targetUserId", - "value": "", - "type": "string" - }, - { - "key": "targetUsername", - "value": "", - "type": "string" - }, - { - "key": "lastFollowingState", - "value": "", - "type": "string" - }, - { - "key": "notificationUnreadCount", - "value": "", - "type": "string" - }, - { - "key": "notificationId", - "value": "", - "type": "string" - }, - { - "key": "postId", - "value": "", - "type": "string" - }, - { - "key": "ownPostId", - "value": "", - "type": "string" - }, - { - "key": "targetPostId", - "value": "", - "type": "string" - }, - { - "key": "adminPostId", - "value": "", - "type": "string" - }, - { - "key": "commentId", - "value": "", - "type": "string" - }, - { - "key": "ownCommentId", - "value": "", - "type": "string" - }, - { - "key": "replyCommentId", - "value": "", - "type": "string" - }, - { - "key": "conversationId", - "value": "", - "type": "string" - }, - { - "key": "messageId", - "value": "", - "type": "string" - }, - { - "key": "sessionJti", - "value": "", - "type": "string" - }, - { - "key": "registerEmail", - "value": "", - "type": "string" - }, - { - "key": "registerUsername", - "value": "", - "type": "string" - }, - { - "key": "adminUserId", - "value": "", - "type": "string" - }, - { - "key": "targetLoginEmail", - "value": "", - "type": "string" - }, - { - "key": "targetLoginPassword", - "value": "", - "type": "string" - }, - { - "key": "resetCode", - "value": "", - "type": "string" - }, - { - "key": "resetToken", - "value": "", - "type": "string" - }, - { - "key": "emailVerificationCode", - "value": "", - "type": "string" - }, - { - "key": "googleIdToken", - "value": "", - "type": "string" - }, - { - "key": "newPassword", - "value": "NewStrongPass123!", - "type": "string" - }, - { - "key": "feedCursor", - "value": "", - "type": "string" - }, - { - "key": "chatConversationsCursor", - "value": "", - "type": "string" - }, - { - "key": "chatMessagesCursor", - "value": "", - "type": "string" - }, - { - "key": "adminAccessToken", - "value": "", - "type": "string" - }, - { - "key": "adminRefreshToken", - "value": "", - "type": "string" - }, - { - "key": "adminEmail", - "value": "store_admin@example.com", - "type": "string" - }, - { - "key": "adminUsername", - "value": "store_admin_01", - "type": "string" - }, - { - "key": "adminPassword", - "value": "AdminStrongPass123!", - "type": "string" - }, - { - "key": "usersQuery", - "value": "user", - "type": "string" - }, - { - "key": "usersVerified", - "value": "true", - "type": "string" - }, - { - "key": "usersSortBy", - "value": "createdAt", - "type": "string" - }, - { - "key": "listSortOrder", - "value": "desc", - "type": "string" - }, - { - "key": "commentSortBy", - "value": "createdAt", - "type": "string" - }, - { - "key": "postVisibility", - "value": "public", - "type": "string" - }, - { - "key": "postSortBy", - "value": "createdAt", - "type": "string" - }, - { - "key": "postTypeFilter", - "value": "image", - "type": "string" - }, - { - "key": "postSearchQuery", - "value": "music", - "type": "string" - }, - { - "key": "postHashtag", - "value": "music", - "type": "string" - }, - { - "key": "reelQuery", - "value": "reel", - "type": "string" - }, - { - "key": "notificationRead", - "value": "false", - "type": "string" - }, - { - "key": "notificationType", - "value": "mention", - "type": "string" - }, - { - "key": "notificationResourceType", - "value": "post", - "type": "string" - }, - { - "key": "feedPreferredPostType", - "value": "video", - "type": "string" - }, - { - "key": "feedIncludeSuggestions", - "value": "true", - "type": "string" - }, - { - "key": "feedSuggestionInterval", - "value": "4", - "type": "string" - }, - { - "key": "feedFollowingOnly", - "value": "false", - "type": "string" - }, - { - "key": "feedRadiusKm", - "value": "30", - "type": "string" - }, - { - "key": "homeFeedHasMixedItems", - "value": "", - "type": "string" - }, - { - "key": "lastViewCount", - "value": "", - "type": "string" - }, - { - "key": "lastPlayCount", - "value": "", - "type": "string" - }, - { - "key": "lastShareCount", - "value": "", - "type": "string" - }, - { - "key": "marketplaceQuery", - "value": "oud", - "type": "string" - }, - { - "key": "marketplaceMinPrice", - "value": "1000", - "type": "string" - }, - { - "key": "marketplaceMaxPrice", - "value": "5000", - "type": "string" - }, - { - "key": "marketplaceIsActive", - "value": "true", - "type": "string" - }, - { - "key": "marketplaceSortBy", - "value": "price", - "type": "string" - }, - { - "key": "repairShopQuery", - "value": "riyadh", - "type": "string" - }, - { - "key": "repairShopIsActive", - "value": "true", - "type": "string" - }, - { - "key": "repairShopSortBy", - "value": "name", - "type": "string" - }, - { - "key": "instrumentId", - "value": "", - "type": "string" - }, - { - "key": "repairShopId", - "value": "", - "type": "string" - }, - { - "key": "generatedMusicUrl", - "value": "", - "type": "string" - }, - { - "key": "aiMusicPrompt", - "value": "Calm oud melody with light percussion and emotional Arabic mood", - "type": "string" - }, - { - "key": "aiMusicDuration", - "value": "12", - "type": "string" - }, - { - "key": "aiMusicSeed", - "value": "42", - "type": "string" - }, - { - "key": "listingId", - "value": "", - "type": "string" - }, - { - "key": "marketplaceListingCategory", - "value": "accessory", - "type": "string" - }, - { - "key": "marketplaceCondition", - "value": "used", - "type": "string" - }, - { - "key": "marketplaceInstrumentType", - "value": "oud", - "type": "string" - }, - { - "key": "talentRole", - "value": "instrumentalist", - "type": "string" - }, - { - "key": "talentExperienceLevel", - "value": "", - "type": "string" - }, - { - "key": "usersHasAvatar", - "value": "true", - "type": "string" - }, - { - "key": "reactionType", - "value": "love", - "type": "string" - }, - { - "key": "followRequestId", - "value": "", - "type": "string" - }, - { - "key": "reportId", - "value": "", - "type": "string" - }, - { - "key": "repostId", - "value": "", - "type": "string" - }, - { - "key": "quotePostId", - "value": "", - "type": "string" - }, - { - "key": "replyToMessageId", - "value": "" - }, - { - "key": "mediaFilePath", - "value": "" - }, - { - "key": "collaboratorId", - "value": "" - }, - { - "key": "commentKeyword", - "value": "spam" - }, - { - "key": "reportReason", - "value": "spam" - }, - { - "key": "sessionId", - "value": "" - }, - { - "key": "caseId", - "value": "" - }, - { - "key": "historyId", - "value": "" - }, - { - "key": "bulkActionTargetId", - "value": "" - }, - { - "key": "auditAction", - "value": "" - } - ] -} \ No newline at end of file + "info": { + "name": "Oudelaa Dashboard API", + "description": "Dashboard/admin collection generated from Oudelaa master collection.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Auth", + "item": [ + { + "name": "Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.user).to.exist;", + "const uid = json.user._id || json.user.id;", + "pm.environment.set('accessToken', json.accessToken);", + "pm.environment.set('refreshToken', json.refreshToken);", + "pm.environment.set('userId', uid);", + "pm.environment.set('currentUserId', uid);" + ] + } + } + ] + }, + { + "name": "Target User Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{targetLoginEmail}}\",\n \"password\": \"{{targetLoginPassword}}\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.user).to.exist;", + "const uid = json.user._id || json.user.id;", + "pm.environment.set('targetAccessToken', json.accessToken);", + "pm.environment.set('targetRefreshToken', json.refreshToken);", + "pm.environment.set('targetUserId', uid);" + ] + } + } + ] + }, + { + "name": "Admin Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{adminEmail}}\",\n \"password\": \"{{adminPassword}}\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.environment.set('adminAccessToken', json.accessToken);", + "pm.environment.set('adminRefreshToken', json.refreshToken);", + "pm.environment.set('adminUserId', (json.user && (json.user._id || json.user.id)) || pm.environment.get('adminUserId'));" + ] + } + } + ] + }, + { + "name": "Refresh", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/refresh", + "body": { + "mode": "raw", + "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" + } + }, + "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.user).to.exist;", + "pm.environment.set('accessToken', json.accessToken);", + "pm.environment.set('refreshToken', json.refreshToken);", + "pm.environment.set('userId', json.user._id || json.user.id);" + ] + } + } + ] + }, + { + "name": "Logout", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/logout", + "body": { + "mode": "raw", + "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" + } + }, + "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.message).to.eql('Logged out successfully');" + ] + } + } + ] + }, + { + "name": "SuperAdmin", + "item": [ + { + "name": "SuperAdmin Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/superadmin/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"admin@oudelaa.com\",\n \"password\": \"SuperAdminStrongPass123!\"\n}" + } + }, + "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.accessToken).to.exist;", + "pm.expect(json.refreshToken).to.exist;", + "pm.environment.set('superAdminAccessToken', json.accessToken);", + "pm.environment.set('superAdminRefreshToken', json.refreshToken);" + ] + } + } + ] + }, + { + "name": "SuperAdmin Refresh", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/superadmin/refresh", + "body": { + "mode": "raw", + "raw": "{\n \"refreshToken\": \"{{superAdminRefreshToken}}\"\n}" + } + }, + "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.accessToken).to.exist;", + "pm.expect(json.refreshToken).to.exist;", + "pm.environment.set('superAdminAccessToken', json.accessToken);", + "pm.environment.set('superAdminRefreshToken', json.refreshToken);" + ] + } + } + ] + }, + { + "name": "SuperAdmin Logout", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/superadmin/logout", + "body": { + "mode": "raw", + "raw": "{\n \"refreshToken\": \"{{superAdminRefreshToken}}\"\n}" + } + }, + "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.message).to.eql('Superadmin logged out successfully');" + ] + } + } + ] + } + ] + }, + { + "name": "SuperAdmin List Sessions", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/auth/superadmin/sessions", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "superadmin", + "sessions" + ] + } + } + }, + { + "name": "SuperAdmin Revoke Session", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/auth/superadmin/sessions/{{sessionId}}/revoke", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "superadmin", + "sessions", + "{{sessionId}}", + "revoke" + ] + } + } + } + ] + }, + { + "name": "Media", + "item": [ + { + "name": "Media Health", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/media/health" + }, + "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.storage).to.be.an('object');", + "pm.expect(json.storage.provider).to.be.oneOf(['local', 's3']);", + "pm.expect(json.serving.rangeRequests).to.eql(true);", + "pm.expect(json.processing).to.be.an('object');" + ] + } + } + ] + } + ] + }, + { + "name": "Search", + "item": [ + { + "name": "Global Search - All", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search?q={{searchQuery}}&type={{searchType}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('query');", + "pm.expect(json).to.have.property('users');", + "pm.expect(json).to.have.property('posts');", + "pm.expect(json).to.have.property('hashtags');", + "pm.expect(json.users.pagination).to.exist;", + "pm.expect(json.posts.pagination).to.exist;", + "pm.expect(json.hashtags.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Global Search - Users Type", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search?q={{searchQuery}}&type=users&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.users).to.have.property('items');", + "pm.expect(json.users).to.have.property('pagination');" + ] + } + } + ] + }, + { + "name": "Search Users", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/users?q={{searchQuery}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " const target = json.items[0];", + " pm.expect(target).to.have.property('username');", + " pm.expect(target).to.have.property('isFollowing');", + " pm.environment.set('targetUserId', target._id || target.id);", + "}" + ] + } + } + ] + }, + { + "name": "Search Posts", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/posts?q={{searchQuery}}&page=1&limit={{searchLimit}}&sortOrder={{listSortOrder}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " const post = json.items[0];", + " pm.expect(post).to.have.property('media');", + " pm.expect(post).to.have.property('isLiked');", + " pm.expect(post).to.have.property('isSaved');", + " pm.environment.set('postId', post._id || post.id);", + "}" + ] + } + } + ] + }, + { + "name": "Search Hashtags", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/hashtags?q={{searchQuery}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " pm.expect(json.items[0]).to.have.property('tag');", + " pm.expect(json.items[0]).to.have.property('postsCount');", + " pm.environment.set('postHashtag', json.items[0].tag);", + "}" + ] + } + } + ] + }, + { + "name": "Search Suggestions", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/suggestions?q={{searchQuery}}&limit={{searchSuggestionLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('users');", + "pm.expect(json).to.have.property('hashtags');" + ] + } + } + ] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "SuperAdmin List Admins", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/admins?page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('adminUserId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "SuperAdmin Update Admin", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/admins/{{adminUserId}}", + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Store Admin Updated\",\n \"stageName\": \"Music Store Owner\",\n \"bio\": \"Admin managing marketplace tools\",\n \"location\": \"Riyadh, Saudi Arabia\"\n}" + } + }, + "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('role');", + "pm.expect(json.role).to.eql('admin');" + ] + } + } + ] + }, + { + "name": "SuperAdmin Delete Admin", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/admins/{{adminUserId}}" + }, + "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.message).to.eql('Admin deleted successfully');" + ] + } + } + ] + }, + { + "name": "SuperAdmin Create Admin", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/create-admin", + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Store Admin\",\n \"username\": \"{{adminUsername}}\",\n \"email\": \"{{adminEmail}}\",\n \"password\": \"{{adminPassword}}\",\n \"confirmPassword\": \"{{adminPassword}}\"\n}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.environment.set('adminUserId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "SuperAdmin Set User Role", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/{{adminUserId}}/role", + "body": { + "mode": "raw", + "raw": "{\n \"role\": \"admin\"\n}" + } + }, + "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.role).to.eql('admin');" + ] + } + } + ] + }, + { + "name": "Admin Get Users", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin?page=1&limit=10" + }, + "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();", + "if (json.items && json.items.length > 0) { pm.environment.set('adminUserId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Admin Get User By Id", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/{{adminUserId}}" + }, + "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._id || json.id).to.exist;" + ] + } + } + ] + }, + { + "name": "Admin Update User", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/{{adminUserId}}", + "body": { + "mode": "raw", + "raw": "{\n \"stageName\": \"Updated by SuperAdmin\",\n \"bio\": \"Profile updated by admin\"\n}" + } + }, + "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.stageName).to.eql('Updated by SuperAdmin');" + ] + } + } + ] + }, + { + "name": "Admin Disable User", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/{{adminUserId}}/disable", + "body": { + "mode": "raw", + "raw": "{\n \"reason\": \"Violation of community guidelines\"\n}" + } + }, + "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.isDisabled).to.eql(true);" + ] + } + } + ] + }, + { + "name": "Admin Enable User", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/{{adminUserId}}/enable" + }, + "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.isDisabled).to.eql(false);" + ] + } + } + ] + }, + { + "name": "Admin Delete User", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/users/admin/{{adminUserId}}" + }, + "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.message).to.eql('User deleted successfully');" + ] + } + } + ] + }, + { + "name": "Admin Discover Users", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/users/admin/discover?page=1&limit=20&q={{usersQuery}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "admin", + "discover" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "q", + "value": "{{usersQuery}}" + } + ] + } + } + }, + { + "name": "Admin Get User Profile Overview", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/users/admin/{{id}}/profile-overview", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "admin", + "{{id}}", + "profile-overview" + ] + } + } + } + ] + }, + { + "name": "Posts", + "item": [ + { + "name": "Admin Get Posts Moderation", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/admin/moderation?page=1&limit=20&moderationStatus=flagged", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "admin", + "moderation" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "moderationStatus", + "value": "flagged" + } + ] + } + } + } + ] + }, + { + "name": "Collaboration Requests", + "item": [ + { + "name": "Create Collaboration Request", + "request": { + "method": "POST", + "url": "{{baseUrl}}/posts/{{postId}}/collaboration-requests", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"targetUserId\": \"{{targetUserId}}\",\n \"collaborationType\": \"{{collaborationType}}\",\n \"message\": \"{{collaborationMessage}}\",\n \"attachmentUrl\": \"{{collaborationAttachmentUrl}}\",\n \"attachmentType\": \"{{collaborationAttachmentType}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200 or 201', function () { pm.expect([200, 201]).to.include(pm.response.code); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('request');", + "pm.environment.set('collaborationRequestId', json.request._id || json.request.id);", + "pm.test('Rich collaboration fields are accepted', function () {", + " pm.expect(json.request).to.have.property('collaborationType');", + " pm.expect(json.request).to.have.property('message');", + " pm.expect(json.request).to.have.property('attachmentUrl');", + " pm.expect(json.request).to.have.property('attachmentType');", + "});" + ] + } + } + ] + }, + { + "name": "List My Collaboration Requests", + "request": { + "method": "GET", + "url": "{{baseUrl}}/collaboration-requests?page=1&limit=20", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);", + " pm.expect(json.items[0]).to.have.property('status');", + " pm.expect(json.items[0]).to.have.property('collaborationType');", + "}" + ] + } + } + ] + }, + { + "name": "Approve Collaboration Request", + "request": { + "method": "PATCH", + "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/approve", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('approved', true);", + "pm.expect(json).to.have.property('request');" + ] + } + } + ] + }, + { + "name": "Reject Collaboration Request", + "request": { + "method": "PATCH", + "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/reject", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('rejected', true);", + "pm.expect(json).to.have.property('request');" + ] + } + } + ] + } + ] + }, + { + "name": "Comments", + "item": [ + { + "name": "Admin Delete Comment", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/comments/admin/{{commentId}}" + }, + "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.success).to.eql(true);" + ] + } + } + ] + }, + { + "name": "Admin List Comments", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/comments/admin?page=1&limit=20&moderationStatus=flagged", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "comments", + "admin" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "moderationStatus", + "value": "flagged" + } + ] + } + } + } + ] + }, + { + "name": "Notifications", + "item": [ + { + "name": "SuperAdmin Notifications", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/notifications/superadmin?page=1&limit=20&type={{notificationType}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "notifications", + "superadmin" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "type", + "value": "{{notificationType}}" + } + ] + } + } + } + ] + }, + { + "name": "Smoke", + "item": [ + { + "name": "Login (Smoke)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\"\n}" + } + }, + "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();", + "const uid = json.user._id || json.user.id;", + "pm.environment.set('accessToken', json.accessToken);", + "pm.environment.set('refreshToken', json.refreshToken);", + "pm.environment.set('userId', uid);" + ] + } + } + ] + } + ] + }, + { + "name": "Cleanup", + "item": [ + { + "name": "Delete Instrument (if exists)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/instruments/{{instrumentId}}" + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('instrumentId')) { postman.setNextRequest(null); }" + ] + } + } + ] + } + ] + }, + { + "name": "Marketplace", + "item": [ + { + "name": "02 Shop Profile", + "item": [ + { + "name": "Admin", + "item": [ + { + "name": "Admin Update Shop Profile", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/shop-profile", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "shopName", + "value": "Awtarna Store", + "type": "text" + }, + { + "key": "shopDescription", + "value": "Trusted marketplace shop profile", + "type": "text" + }, + { + "key": "shopLocation", + "value": "Riyadh", + "type": "text" + }, + { + "key": "shopLatitude", + "value": "24.7136", + "type": "text" + }, + { + "key": "shopLongitude", + "value": "46.6753", + "type": "text" + }, + { + "key": "shopImageFiles", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "shopImageFiles", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('adminAccessToken')) { throw new Error('Missing environment var: adminAccessToken'); }" + ] + } + }, + { + "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.shopName).to.eql('Oudelaa Music Store');" + ] + } + } + ] + }, + { + "name": "Admin Get My Shop Profile", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/shop-profile/me" + }, + "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('shopName');" + ] + } + } + ] + } + ] + }, + { + "name": "Public", + "item": [ + { + "name": "Public Get Shop By Admin Id", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/marketplace/shops/{{adminUserId}}" + }, + "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('shopName');" + ] + } + } + ] + } + ] + } + ] + }, + { + "name": "03 Repair Shops", + "item": [ + { + "name": "Admin", + "item": [ + { + "name": "Admin Create Repair Shop (One Shop Per Admin)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/repair-shops", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "name", + "value": "Fix Strings Workshop", + "type": "text" + }, + { + "key": "description", + "value": "Repair shop for oud and violin", + "type": "text" + }, + { + "key": "services", + "value": "repair,setup,cleaning", + "type": "text" + }, + { + "key": "phone", + "value": "+966500000000", + "type": "text" + }, + { + "key": "whatsapp", + "value": "+966500000000", + "type": "text" + }, + { + "key": "location", + "value": "Riyadh", + "type": "text" + }, + { + "key": "latitude", + "value": "24.7136", + "type": "text" + }, + { + "key": "longitude", + "value": "46.6753", + "type": "text" + }, + { + "key": "isActive", + "value": "true", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('adminAccessToken')) { throw new Error('Missing environment var: adminAccessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.environment.set('repairShopId', json._id || json.id);", + "pm.expect(json.name).to.eql('Oudelaa Repair Center');" + ] + } + } + ] + }, + { + "name": "Admin Create Second Repair Shop Should Fail", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/repair-shops", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "name", + "value": "Fix Strings Workshop", + "type": "text" + }, + { + "key": "description", + "value": "Repair shop for oud and violin", + "type": "text" + }, + { + "key": "services", + "value": "repair,setup,cleaning", + "type": "text" + }, + { + "key": "phone", + "value": "+966500000000", + "type": "text" + }, + { + "key": "whatsapp", + "value": "+966500000000", + "type": "text" + }, + { + "key": "location", + "value": "Riyadh", + "type": "text" + }, + { + "key": "latitude", + "value": "24.7136", + "type": "text" + }, + { + "key": "longitude", + "value": "46.6753", + "type": "text" + }, + { + "key": "isActive", + "value": "true", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('adminAccessToken')) { throw new Error('Missing environment var: adminAccessToken'); }", + "if (!pm.environment.get('repairShopId')) { throw new Error('Create the first repair shop before running this negative test.'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 400', function () { pm.response.to.have.status(400); });", + "const json = pm.response.json();", + "const messages = Array.isArray(json.message) ? json.message : [json.message];", + "pm.expect(messages.join(' ')).to.include('Each admin can create one marketplace shop only');" + ] + } + } + ] + }, + { + "name": "Admin Update Repair Shop", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/repair-shops/{{repairShopId}}", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "name", + "value": "Updated Fix Strings Workshop", + "type": "text" + }, + { + "key": "description", + "value": "Updated repair shop description", + "type": "text" + }, + { + "key": "services", + "value": "repair,setup,polishing", + "type": "text" + }, + { + "key": "phone", + "value": "+966511111111", + "type": "text" + }, + { + "key": "whatsapp", + "value": "+966511111111", + "type": "text" + }, + { + "key": "location", + "value": "Jeddah", + "type": "text" + }, + { + "key": "latitude", + "value": "21.5433", + "type": "text" + }, + { + "key": "longitude", + "value": "39.1728", + "type": "text" + }, + { + "key": "isActive", + "value": "true", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Admin Get My Repair Shops", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/repair-shops/me?q={{repairShopQuery}}&isActive={{repairShopIsActive}}&sortBy={{repairShopSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('repairShopId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Admin Delete Repair Shop", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/repair-shops/{{repairShopId}}" + }, + "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.success).to.eql(true);" + ] + } + } + ] + } + ] + } + ] + }, + { + "name": "04 Musical Instruments", + "item": [ + { + "name": "Admin", + "item": [ + { + "name": "Admin Create Instrument", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/instruments", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "title", + "value": "Concert Guitar", + "type": "text" + }, + { + "key": "description", + "value": "Acoustic guitar in excellent condition", + "type": "text" + }, + { + "key": "price", + "value": "2100", + "type": "text" + }, + { + "key": "currency", + "value": "SAR", + "type": "text" + }, + { + "key": "quantity", + "value": "1", + "type": "text" + }, + { + "key": "condition", + "value": "used", + "type": "text" + }, + { + "key": "instrumentType", + "value": "Guitar", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('adminAccessToken')) { throw new Error('Missing environment var: adminAccessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.environment.set('instrumentId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "Admin Update Instrument", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/instruments/{{instrumentId}}", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "title", + "value": "Updated Concert Guitar", + "type": "text" + }, + { + "key": "description", + "value": "Updated instrument description", + "type": "text" + }, + { + "key": "price", + "value": "2400", + "type": "text" + }, + { + "key": "currency", + "value": "SAR", + "type": "text" + }, + { + "key": "quantity", + "value": "1", + "type": "text" + }, + { + "key": "condition", + "value": "used", + "type": "text" + }, + { + "key": "instrumentType", + "value": "Violin", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Admin Get My Instruments", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/instruments/me?q={{marketplaceQuery}}&minPrice={{marketplaceMinPrice}}&maxPrice={{marketplaceMaxPrice}}&isActive={{marketplaceIsActive}}&condition={{marketplaceCondition}}&instrumentType={{marketplaceInstrumentType}}&sortBy={{marketplaceSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('instrumentId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Admin Delete Instrument", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/instruments/{{instrumentId}}" + }, + "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.success).to.eql(true);" + ] + } + } + ] + } + ] + } + ] + }, + { + "name": "05 General Marketplace", + "item": [ + { + "name": "Admin", + "item": [ + { + "name": "Admin Create Listing", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/listings", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "title", + "value": "Microphone SM58", + "type": "text" + }, + { + "key": "description", + "value": "Durable dynamic microphone for studio and stage", + "type": "text" + }, + { + "key": "price", + "value": "450", + "type": "text" + }, + { + "key": "currency", + "value": "SAR", + "type": "text" + }, + { + "key": "quantity", + "value": "1", + "type": "text" + }, + { + "key": "condition", + "value": "used", + "type": "text" + }, + { + "key": "instrumentType", + "value": "Microphone", + "type": "text" + }, + { + "key": "listingCategory", + "value": "audio_gear", + "type": "text" + }, + { + "key": "isActive", + "value": "true", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('adminAccessToken')) { throw new Error('Missing environment var: adminAccessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.environment.set('listingId', json._id || json.id);", + "pm.expect(json.listingCategory).to.eql('accessory');" + ] + } + } + ] + }, + { + "name": "Admin Update Listing", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/listings/{{listingId}}", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "title", + "value": "Updated Microphone SM58", + "type": "text" + }, + { + "key": "description", + "value": "Updated marketplace listing description", + "type": "text" + }, + { + "key": "price", + "value": "500", + "type": "text" + }, + { + "key": "currency", + "value": "SAR", + "type": "text" + }, + { + "key": "quantity", + "value": "1", + "type": "text" + }, + { + "key": "condition", + "value": "used", + "type": "text" + }, + { + "key": "instrumentType", + "value": "Microphone", + "type": "text" + }, + { + "key": "listingCategory", + "value": "audio_gear", + "type": "text" + }, + { + "key": "isActive", + "value": "true", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "imageFiles", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Admin Get My Listings", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/listings/me?q={{marketplaceQuery}}&minPrice={{marketplaceMinPrice}}&maxPrice={{marketplaceMaxPrice}}&isActive={{marketplaceIsActive}}&listingCategory={{marketplaceListingCategory}}&condition={{marketplaceCondition}}&instrumentType={{marketplaceInstrumentType}}&sortBy={{marketplaceSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('listingId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Admin Delete Listing", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{adminAccessToken}}" + } + ], + "url": "{{baseUrl}}/marketplace/admin/listings/{{listingId}}" + }, + "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.success).to.eql(true);" + ] + } + } + ] + } + ] + } + ] + }, + { + "name": "SuperAdmin List Marketplace Listings", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/listings?page=1&limit=20", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "listings" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + } + ] + } + } + }, + { + "name": "SuperAdmin Update Listing Status", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/listings/{{id}}/status", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "listings", + "{{id}}", + "status" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"isActive\": false,\n \"moderationStatus\": \"flagged\",\n \"moderationReason\": \"Needs marketplace review\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "SuperAdmin Delete Listing", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/listings/{{id}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "listings", + "{{id}}" + ] + } + } + }, + { + "name": "SuperAdmin List Repair Shops", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/repair-shops?page=1&limit=20", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "repair-shops" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + } + ] + } + } + }, + { + "name": "SuperAdmin Update Repair Shop Status", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/repair-shops/{{id}}/status", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "repair-shops", + "{{id}}", + "status" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"isActive\": false,\n \"moderationStatus\": \"flagged\",\n \"moderationReason\": \"Needs marketplace review\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "SuperAdmin Delete Repair Shop", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/repair-shops/{{id}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "repair-shops", + "{{id}}" + ] + } + } + }, + { + "name": "SuperAdmin Create Listing For Admin", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/admins/{{adminId}}/listings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "admins", + "{{adminId}}", + "listings" + ] + }, + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "title", + "value": "Superadmin listing", + "type": "text" + }, + { + "key": "description", + "value": "Created by superadmin", + "type": "text" + }, + { + "key": "price", + "value": "1000", + "type": "text" + }, + { + "key": "currency", + "value": "SAR", + "type": "text" + }, + { + "key": "quantity", + "value": "1", + "type": "text" + }, + { + "key": "listingCategory", + "value": "general", + "type": "text" + } + ] + } + } + }, + { + "name": "SuperAdmin Create Instrument For Admin", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/admins/{{adminId}}/instruments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "admins", + "{{adminId}}", + "instruments" + ] + }, + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "title", + "value": "Superadmin instrument", + "type": "text" + }, + { + "key": "description", + "value": "Created by superadmin", + "type": "text" + }, + { + "key": "price", + "value": "1200", + "type": "text" + }, + { + "key": "currency", + "value": "SAR", + "type": "text" + }, + { + "key": "quantity", + "value": "1", + "type": "text" + }, + { + "key": "instrumentType", + "value": "Oud", + "type": "text" + } + ] + } + } + }, + { + "name": "SuperAdmin Create Repair Shop For Admin", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/admins/{{adminId}}/repair-shops", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "admins", + "{{adminId}}", + "repair-shops" + ] + }, + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "name", + "value": "Superadmin Repair Shop", + "type": "text" + }, + { + "key": "description", + "value": "Created by superadmin", + "type": "text" + }, + { + "key": "services", + "value": "oud repair,string replacement", + "type": "text" + }, + { + "key": "location", + "value": "Riyadh", + "type": "text" + } + ] + } + } + }, + { + "name": "SuperAdmin Update Admin Shop Profile", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/marketplace/superadmin/admins/{{adminId}}/shop-profile", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "marketplace", + "superadmin", + "admins", + "{{adminId}}", + "shop-profile" + ] + }, + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "shopName", + "value": "Superadmin Shop", + "type": "text" + }, + { + "key": "shopDescription", + "value": "Updated by superadmin", + "type": "text" + }, + { + "key": "shopLocation", + "value": "Riyadh", + "type": "text" + } + ] + } + } + } + ] + }, + { + "name": "Reports", + "item": [ + { + "name": "Superadmin Reports", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": "{{baseUrl}}/reports/superadmin?status=open" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Superadmin Update Report Status", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/reports/superadmin/{{reportId}}/status", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "reports", + "superadmin", + "{{reportId}}", + "status" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"in_review\",\n \"resolutionNote\": \"Under review\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Audit", + "item": [ + { + "name": "SuperAdmin Audit Logs", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/audit/superadmin/logs?page=1&limit=20&action={{auditAction}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "audit", + "superadmin", + "logs" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "action", + "value": "{{auditAction}}" + } + ] + } + } + } + ] + }, + { + "name": "SuperAdmin", + "item": [ + { + "name": "Get SuperAdmin Session", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/session", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "session" + ] + } + } + }, + { + "name": "Get SuperAdmin Overview", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/overview", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "overview" + ] + } + } + }, + { + "name": "Get SuperAdmin Charts", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/charts?range=30d", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "charts" + ], + "query": [ + { + "key": "range", + "value": "30d" + } + ] + } + } + }, + { + "name": "Get SuperAdmin Recent Activity", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/recent-activity?page=1&limit=20", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "recent-activity" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + } + ] + } + } + }, + { + "name": "Get SuperAdmin Reports", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/reports?range=30d", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "reports" + ], + "query": [ + { + "key": "range", + "value": "30d" + } + ] + } + } + }, + { + "name": "Get SuperAdmin Ops", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/ops", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "ops" + ] + } + } + }, + { + "name": "List SuperAdmin Cases", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/cases?page=1&limit=20&status=open", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "cases" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "status", + "value": "open" + } + ] + } + } + }, + { + "name": "Create SuperAdmin Case", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/cases", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "cases" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"title\": \"Manual review\",\n \"description\": \"Created from Postman\",\n \"targetType\": \"post\",\n \"targetId\": \"{{postId}}\",\n \"priority\": \"medium\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Update SuperAdmin Case", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/cases/{{caseId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "cases", + "{{caseId}}" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"in_review\",\n \"resolutionNote\": \"Updated from Postman\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Perform SuperAdmin Bulk Action", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/bulk-actions", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "bulk-actions" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"targetType\": \"post\",\n \"targetIds\": [\n \"{{bulkActionTargetId}}\"\n ],\n \"action\": \"flag\",\n \"reason\": \"Bulk moderation from Postman\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get SuperAdmin Settings", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/settings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "settings" + ] + } + } + }, + { + "name": "Get SuperAdmin Settings History", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/settings/history?page=1&limit=20", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "settings", + "history" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + } + ] + } + } + }, + { + "name": "Update SuperAdmin Settings", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/settings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "settings" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"maintenanceMode\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Restore SuperAdmin Settings Version", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/settings/history/{{historyId}}/restore", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "settings", + "history", + "{{historyId}}", + "restore" + ] + } + } + }, + { + "name": "Update Post Moderation Status", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/posts/{{postId}}/status", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "posts", + "{{postId}}", + "status" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"flagged\",\n \"reason\": \"Needs review\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete Post By SuperAdmin", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/posts/{{postId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "posts", + "{{postId}}" + ] + } + } + }, + { + "name": "Restore Post By SuperAdmin", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/posts/{{postId}}/restore", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "posts", + "{{postId}}", + "restore" + ] + } + } + }, + { + "name": "Update Comment Moderation Status", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/comments/{{commentId}}/status", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "comments", + "{{commentId}}", + "status" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"flagged\",\n \"reason\": \"Needs review\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete Comment By SuperAdmin", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/comments/{{commentId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "comments", + "{{commentId}}" + ] + } + } + }, + { + "name": "Restore Comment By SuperAdmin", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/comments/{{commentId}}/restore", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "comments", + "{{commentId}}", + "restore" + ] + } + } + }, + { + "name": "Update User Status By SuperAdmin", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{superAdminAccessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/superadmin/users/{{userId}}/status", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "superadmin", + "users", + "{{userId}}", + "status" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"isDisabled\": true,\n \"reason\": \"Policy violation\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "oudelaa_dashboard", + "item": [ + { + "name": "Follow System", + "item": [ + { + "name": "Follow User", + "request": { + "method": "POST", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{targetUserId}}/follow", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Follow response', function () { const json = pm.response.json(); pm.expect(json).to.have.property('isFollowing'); pm.expect(json).to.have.property('targetUserId'); });" + ] + } + } + }, + { + "name": "Unfollow User", + "request": { + "method": "DELETE", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{targetUserId}}/follow", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Unfollow response', function () { const json = pm.response.json(); pm.expect(json.isFollowing).to.eql(false); });" + ] + } + } + }, + { + "name": "Get Follow Status", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{targetUserId}}/follow-status", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Follow status shape', function () { const json = pm.response.json(); pm.expect(json).to.have.property('isFollowing'); pm.expect(json).to.have.property('isFollowedBy'); pm.expect(json).to.have.property('isMutual'); });" + ] + } + } + }, + { + "name": "Get User Followers", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/followers?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Get User Following", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/following?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Get My Followers", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/me/followers?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Get My Following", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/me/following?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Legacy Toggle Follow", + "request": { + "method": "POST", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/follows/toggle", + "body": { + "mode": "raw", + "raw": "{\n \"targetUserId\": \"{{targetUserId}}\"\n}" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "List Follow Requests", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/follows/requests?page=1&limit=20", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Approve Follow Request", + "request": { + "method": "PATCH", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/follows/requests/{{followRequestId}}/approve", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Reject Follow Request", + "request": { + "method": "PATCH", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/follows/requests/{{followRequestId}}/reject", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + } + ] + }, + { + "name": "Presence & Profile", + "item": [ + { + "name": "Get User Presence", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/presence", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Presence shape', function () { const json = pm.response.json(); pm.expect(json).to.have.property('userId'); pm.expect(json).to.have.property('isOnline'); pm.expect(json).to.have.property('lastSeenAt'); });" + ] + } + } + }, + { + "name": "Get User Profile With isFollowing", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Get Profile Overview With Presence/Follow State", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/profile-overview", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + } + ] + }, + { + "name": "User Posts", + "item": [ + { + "name": "Get Posts By User Id", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/posts?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Paginated posts', function () { const json = pm.response.json(); pm.expect(json).to.have.property('items'); pm.expect(json).to.have.property('pagination'); });" + ] + } + } + }, + { + "name": "Legacy Get Posts By User Id", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/posts/user/{{userId}}?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + } + ] + }, + { + "name": "Devices FCM", + "item": [ + { + "name": "Register Device FCM Token", + "request": { + "method": "POST", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/devices/register", + "body": { + "mode": "raw", + "raw": "{\n \"fcmToken\": \"{{fcmToken}}\",\n \"platform\": \"android\",\n \"deviceId\": \"{{deviceId}}\",\n \"appVersion\": \"1.0.0\",\n \"locale\": \"ar\"\n}" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Device registered', function () { const json = pm.response.json(); pm.expect(json.message).to.eql('Device registered successfully'); pm.expect(json).to.have.property('device'); });" + ] + } + } + }, + { + "name": "Unregister Device FCM Token", + "request": { + "method": "POST", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/devices/unregister", + "body": { + "mode": "raw", + "raw": "{\n \"fcmToken\": \"{{fcmToken}}\"\n}" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Device unregistered', function () { const json = pm.response.json(); pm.expect(json.message).to.eql('Device unregistered successfully'); });" + ] + } + } + } + ] + }, + { + "name": "Notifications", + "item": [ + { + "name": "Get My Notifications", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/notifications?read={{notificationRead}}&sortOrder={{listSortOrder}}&page=1&limit=20&type={{notificationType}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Get My Unread Notifications Count", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/notifications/unread-count", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Mark Notification Read", + "request": { + "method": "PATCH", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/notifications/{{notificationId}}/read", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Mark All Notifications Read", + "request": { + "method": "PATCH", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/notifications/read-all", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "SuperAdmin Notifications (Dashboard Only)", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/notifications/superadmin?page=1&limit=20&type={{notificationType}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + } + ] + }, + { + "name": "Collaboration Requests", + "item": [ + { + "name": "Create Collaboration Request", + "request": { + "method": "POST", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/posts/{{postId}}/collaboration-requests", + "body": { + "mode": "raw", + "raw": "{\n \"targetUserId\": \"{{targetUserId}}\"\n}" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Collaboration request response', function () { const json = pm.response.json(); pm.expect(json).to.have.property('request'); });" + ] + } + } + }, + { + "name": "List My Collaboration Requests", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/collaboration-requests?page=1&limit=20", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Approve Collaboration Request", + "request": { + "method": "PATCH", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/approve", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Approved', function () { const json = pm.response.json(); pm.expect(json.approved).to.eql(true); });" + ] + } + } + }, + { + "name": "Reject Collaboration Request", + "request": { + "method": "PATCH", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/reject", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Rejected', function () { const json = pm.response.json(); pm.expect(json.rejected).to.eql(true); });" + ] + } + } + } + ] + } + ] + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:4000/api/v1", + "type": "string" + }, + { + "key": "accessToken", + "value": "", + "type": "string" + }, + { + "key": "refreshToken", + "value": "", + "type": "string" + }, + { + "key": "superAdminAccessToken", + "value": "", + "type": "string" + }, + { + "key": "superAdminRefreshToken", + "value": "", + "type": "string" + }, + { + "key": "targetAccessToken", + "value": "", + "type": "string" + }, + { + "key": "targetRefreshToken", + "value": "", + "type": "string" + }, + { + "key": "userId", + "value": "", + "type": "string" + }, + { + "key": "currentUserId", + "value": "", + "type": "string" + }, + { + "key": "targetUserId", + "value": "", + "type": "string" + }, + { + "key": "targetUsername", + "value": "", + "type": "string" + }, + { + "key": "lastFollowingState", + "value": "", + "type": "string" + }, + { + "key": "notificationUnreadCount", + "value": "", + "type": "string" + }, + { + "key": "notificationId", + "value": "", + "type": "string" + }, + { + "key": "postId", + "value": "", + "type": "string" + }, + { + "key": "ownPostId", + "value": "", + "type": "string" + }, + { + "key": "targetPostId", + "value": "", + "type": "string" + }, + { + "key": "adminPostId", + "value": "", + "type": "string" + }, + { + "key": "commentId", + "value": "", + "type": "string" + }, + { + "key": "ownCommentId", + "value": "", + "type": "string" + }, + { + "key": "replyCommentId", + "value": "", + "type": "string" + }, + { + "key": "conversationId", + "value": "", + "type": "string" + }, + { + "key": "messageId", + "value": "", + "type": "string" + }, + { + "key": "sessionJti", + "value": "", + "type": "string" + }, + { + "key": "registerEmail", + "value": "", + "type": "string" + }, + { + "key": "registerUsername", + "value": "", + "type": "string" + }, + { + "key": "adminUserId", + "value": "", + "type": "string" + }, + { + "key": "targetLoginEmail", + "value": "", + "type": "string" + }, + { + "key": "targetLoginPassword", + "value": "", + "type": "string" + }, + { + "key": "resetCode", + "value": "", + "type": "string" + }, + { + "key": "resetToken", + "value": "", + "type": "string" + }, + { + "key": "emailVerificationCode", + "value": "", + "type": "string" + }, + { + "key": "googleIdToken", + "value": "", + "type": "string" + }, + { + "key": "newPassword", + "value": "NewStrongPass123!", + "type": "string" + }, + { + "key": "feedCursor", + "value": "", + "type": "string" + }, + { + "key": "chatConversationsCursor", + "value": "", + "type": "string" + }, + { + "key": "chatMessagesCursor", + "value": "", + "type": "string" + }, + { + "key": "adminAccessToken", + "value": "", + "type": "string" + }, + { + "key": "adminRefreshToken", + "value": "", + "type": "string" + }, + { + "key": "adminEmail", + "value": "store_admin@example.com", + "type": "string" + }, + { + "key": "adminUsername", + "value": "store_admin_01", + "type": "string" + }, + { + "key": "adminPassword", + "value": "AdminStrongPass123!", + "type": "string" + }, + { + "key": "usersQuery", + "value": "user", + "type": "string" + }, + { + "key": "usersVerified", + "value": "true", + "type": "string" + }, + { + "key": "usersSortBy", + "value": "createdAt", + "type": "string" + }, + { + "key": "listSortOrder", + "value": "desc", + "type": "string" + }, + { + "key": "commentSortBy", + "value": "createdAt", + "type": "string" + }, + { + "key": "postVisibility", + "value": "public", + "type": "string" + }, + { + "key": "postSortBy", + "value": "createdAt", + "type": "string" + }, + { + "key": "postTypeFilter", + "value": "image", + "type": "string" + }, + { + "key": "postSearchQuery", + "value": "music", + "type": "string" + }, + { + "key": "postHashtag", + "value": "music", + "type": "string" + }, + { + "key": "reelQuery", + "value": "reel", + "type": "string" + }, + { + "key": "notificationRead", + "value": "false", + "type": "string" + }, + { + "key": "notificationType", + "value": "mention", + "type": "string" + }, + { + "key": "notificationResourceType", + "value": "post", + "type": "string" + }, + { + "key": "feedPreferredPostType", + "value": "video", + "type": "string" + }, + { + "key": "feedIncludeSuggestions", + "value": "true", + "type": "string" + }, + { + "key": "feedSuggestionInterval", + "value": "4", + "type": "string" + }, + { + "key": "feedFollowingOnly", + "value": "false", + "type": "string" + }, + { + "key": "feedRadiusKm", + "value": "30", + "type": "string" + }, + { + "key": "homeFeedHasMixedItems", + "value": "", + "type": "string" + }, + { + "key": "lastViewCount", + "value": "", + "type": "string" + }, + { + "key": "lastPlayCount", + "value": "", + "type": "string" + }, + { + "key": "lastShareCount", + "value": "", + "type": "string" + }, + { + "key": "marketplaceQuery", + "value": "oud", + "type": "string" + }, + { + "key": "marketplaceMinPrice", + "value": "1000", + "type": "string" + }, + { + "key": "marketplaceMaxPrice", + "value": "5000", + "type": "string" + }, + { + "key": "marketplaceIsActive", + "value": "true", + "type": "string" + }, + { + "key": "marketplaceSortBy", + "value": "price", + "type": "string" + }, + { + "key": "repairShopQuery", + "value": "riyadh", + "type": "string" + }, + { + "key": "repairShopIsActive", + "value": "true", + "type": "string" + }, + { + "key": "repairShopSortBy", + "value": "name", + "type": "string" + }, + { + "key": "instrumentId", + "value": "", + "type": "string" + }, + { + "key": "repairShopId", + "value": "", + "type": "string" + }, + { + "key": "generatedMusicUrl", + "value": "", + "type": "string" + }, + { + "key": "aiMusicPrompt", + "value": "Calm oud melody with light percussion and emotional Arabic mood", + "type": "string" + }, + { + "key": "aiMusicDuration", + "value": "12", + "type": "string" + }, + { + "key": "aiMusicSeed", + "value": "42", + "type": "string" + }, + { + "key": "listingId", + "value": "", + "type": "string" + }, + { + "key": "marketplaceListingCategory", + "value": "accessory", + "type": "string" + }, + { + "key": "marketplaceCondition", + "value": "used", + "type": "string" + }, + { + "key": "marketplaceInstrumentType", + "value": "oud", + "type": "string" + }, + { + "key": "talentRole", + "value": "instrumentalist", + "type": "string" + }, + { + "key": "talentExperienceLevel", + "value": "", + "type": "string" + }, + { + "key": "usersHasAvatar", + "value": "true", + "type": "string" + }, + { + "key": "reactionType", + "value": "love", + "type": "string" + }, + { + "key": "followRequestId", + "value": "", + "type": "string" + }, + { + "key": "reportId", + "value": "", + "type": "string" + }, + { + "key": "repostId", + "value": "", + "type": "string" + }, + { + "key": "quotePostId", + "value": "", + "type": "string" + }, + { + "key": "replyToMessageId", + "value": "" + }, + { + "key": "mediaFilePath", + "value": "" + }, + { + "key": "collaboratorId", + "value": "" + }, + { + "key": "commentKeyword", + "value": "spam" + }, + { + "key": "reportReason", + "value": "spam" + }, + { + "key": "sessionId", + "value": "" + }, + { + "key": "caseId", + "value": "" + }, + { + "key": "historyId", + "value": "" + }, + { + "key": "bulkActionTargetId", + "value": "" + }, + { + "key": "auditAction", + "value": "" + }, + { + "key": "searchQuery", + "value": "music" + }, + { + "key": "searchType", + "value": "all" + }, + { + "key": "searchLimit", + "value": "20" + }, + { + "key": "searchSuggestionLimit", + "value": "5" + }, + { + "key": "collaborationType", + "value": "duet" + }, + { + "key": "collaborationMessage", + "value": "I want to collaborate on this piece" + }, + { + "key": "collaborationAttachmentUrl", + "value": "" + }, + { + "key": "collaborationAttachmentType", + "value": "audio" + } + ] +} diff --git a/postman/Oudelaa-Mobile.postman_collection.json b/postman/Oudelaa-Mobile.postman_collection.json index 0e54214..522d8af 100644 --- a/postman/Oudelaa-Mobile.postman_collection.json +++ b/postman/Oudelaa-Mobile.postman_collection.json @@ -1,6252 +1,6406 @@ { - "info": { - "name": "Oudelaa Mobile API", - "description": "Mobile/API app collection generated from Oudelaa master collection.", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "Auth", - "item": [ - { - "name": "Register Basic", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/register-basic", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\",\n \"confirmPassword\": \"StrongPass123!\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const ts = Date.now();", - "if (!pm.environment.get(\u0027registerEmail\u0027)) { pm.environment.set(\u0027registerEmail\u0027, `test_${ts}@example.com`); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.exist;", - "pm.expect(json.email).to.exist;", - "if (json.debugCode) { pm.environment.set(\u0027emailVerificationCode\u0027, json.debugCode); }" - ] - } - } - ] - }, - { - "name": "Register", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/register", - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Test User\",\n \"stageName\": \"Artist One\",\n \"username\": \"{{registerUsername}}\",\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\",\n \"confirmPassword\": \"StrongPass123!\",\n \"musicRoles\": [\"singer\", \"composer\"],\n \"musicGenres\": [\"Tarab\", \"Pop\"],\n \"favoriteInstruments\": [\"Oud\", \"Piano\"],\n \"favoriteMaqamat\": [\"Bayati\", \"Rast\"],\n \"location\": \"Riyadh, Saudi Arabia\",\n \"latitude\": 24.7136,\n \"longitude\": 46.6753,\n \"isPrivate\": false\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const ts = Date.now();", - "if (!pm.environment.get(\u0027registerEmail\u0027)) { pm.environment.set(\u0027registerEmail\u0027, `test_${ts}@example.com`); }", - "if (!pm.environment.get(\u0027registerUsername\u0027)) { pm.environment.set(\u0027registerUsername\u0027, `test_user_${ts}`); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.exist;", - "pm.expect(json.email).to.exist;", - "if (json.debugCode) { pm.environment.set(\u0027emailVerificationCode\u0027, json.debugCode); }" - ] - } - } - ] - }, - { - "name": "Login", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.user).to.exist;", - "const uid = json.user._id || json.user.id;", - "pm.environment.set(\u0027accessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027refreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027userId\u0027, uid);", - "pm.environment.set(\u0027currentUserId\u0027, uid);" - ] - } - } - ] - }, - { - "name": "Target User Login", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{targetLoginEmail}}\",\n \"password\": \"{{targetLoginPassword}}\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.user).to.exist;", - "const uid = json.user._id || json.user.id;", - "pm.environment.set(\u0027targetAccessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027targetRefreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027targetUserId\u0027, uid);" - ] - } - } - ] - }, - { - "name": "Admin Login", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{adminEmail}}\",\n \"password\": \"{{adminPassword}}\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027adminAccessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027adminRefreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027adminUserId\u0027, (json.user \u0026\u0026 (json.user._id || json.user.id)) || pm.environment.get(\u0027adminUserId\u0027));" - ] - } - } - ] - }, - { - "name": "Refresh", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/refresh", - "body": { - "mode": "raw", - "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.user).to.exist;", - "pm.environment.set(\u0027accessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027refreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027userId\u0027, json.user._id || json.user.id);" - ] - } - } - ] - }, - { - "name": "Logout", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/logout", - "body": { - "mode": "raw", - "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.eql(\u0027Logged out successfully\u0027);" - ] - } - } - ] - }, - { - "name": "Google OAuth Start (Browser)", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/auth/google", - "description": "Open this request in browser flow to start Google OAuth redirect." - } - }, - { - "name": "Google OAuth Callback (Browser Redirect)", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/auth/google/callback", - "description": "Callback endpoint is called by Google redirect after browser login. Do not call directly from Postman." - } - }, - { - "name": "Google Token Login (Mobile - Recommended)", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/google/token", - "description": "Mobile-style Google login (Instagram-like): send Google idToken from app SDK.", - "body": { - "mode": "raw", - "raw": "{\n \"idToken\": \"{{googleIdToken}}\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const idToken = (pm.environment.get(\u0027googleIdToken\u0027) || \u0027\u0027).trim();", - "if (!idToken) {", - " throw new Error(\u0027googleIdToken is empty. Paste Google idToken from Flutter/Google SDK into environment variable googleIdToken.\u0027);", - "}" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.accessToken).to.exist;", - "pm.expect(json.refreshToken).to.exist;", - "pm.expect(json.user).to.exist;", - "const uid = json.user._id || json.user.id;", - "pm.environment.set(\u0027accessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027refreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027userId\u0027, uid);", - "pm.environment.set(\u0027currentUserId\u0027, uid);" - ] - } - } - ] - }, - { - "name": "List My Sessions", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/auth/sessions" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027sessionJti\u0027, json.items[0].jti); }" - ] - } - } - ] - }, - { - "name": "Revoke My Session", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/auth/sessions/{{sessionJti}}/revoke" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);" - ] - } - } - ] - }, - { - "name": "Forgot Password", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/forgot-password", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.exist;", - "if (json.debugCode) { pm.environment.set(\u0027resetCode\u0027, json.debugCode); }" - ] - } - } - ] - }, - { - "name": "Verify Reset Code", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/verify-reset-code", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"code\": \"{{resetCode}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.resetToken).to.exist;", - "pm.environment.set(\u0027resetToken\u0027, json.resetToken);" - ] - } - } - ] - }, - { - "name": "Reset Password", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/reset-password", - "body": { - "mode": "raw", - "raw": "{\n \"resetToken\": \"{{resetToken}}\",\n \"newPassword\": \"{{newPassword}}\",\n \"confirmPassword\": \"{{newPassword}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.eql(\u0027Password reset successfully\u0027);" - ] - } - } - ] - }, - { - "name": "Send Email Verification", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/send-email-verification", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.exist;", - "if (json.debugCode) { pm.environment.set(\u0027emailVerificationCode\u0027, json.debugCode); }" - ] - } - } - ] - }, - { - "name": "Verify Email", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/verify-email", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"code\": \"{{emailVerificationCode}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.message).to.exist;", - "if (json.accessToken) { pm.environment.set(\u0027accessToken\u0027, json.accessToken); }", - "if (json.refreshToken) { pm.environment.set(\u0027refreshToken\u0027, json.refreshToken); }", - "if (json.user) { const uid = json.user._id || json.user.id; pm.environment.set(\u0027userId\u0027, uid); pm.environment.set(\u0027currentUserId\u0027, uid); }" - ] - } - } - ] - } - ] - }, - { - "name": "Media", - "item": [ - { - "name": "Media Health", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/media/health" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.storage).to.be.an(\u0027object\u0027);", - "pm.expect(json.storage.provider).to.be.oneOf([\u0027local\u0027, \u0027s3\u0027]);", - "pm.expect(json.serving.rangeRequests).to.eql(true);", - "pm.expect(json.processing).to.be.an(\u0027object\u0027);" - ] - } - } - ] - } - ] - }, - { - "name": "Users", - "item": [ - { - "name": "Search Users", - "request": { - "method": "GET", - "url": "{{baseUrl}}/users?q={{usersQuery}}\u0026isVerified={{usersVerified}}\u0026musicRole={{talentRole}}\u0026experienceLevel={{talentExperienceLevel}}\u0026hasAvatar={{usersHasAvatar}}\u0026sortBy={{usersSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.pagination).to.exist;", - "const currentUserId = pm.environment.get(\u0027currentUserId\u0027);", - "if (json.items \u0026\u0026 json.items.length \u003e 0) {", - " const target = json.items.find(u =\u003e (u._id || u.id) !== currentUserId) || json.items[0];", - " const targetId = target._id || target.id;", - " pm.environment.set(\u0027targetUserId\u0027, targetId);", - " if (target.username) { pm.environment.set(\u0027targetUsername\u0027, target.username); }", - "}" - ] - } - } - ] - }, - { - "name": "Profile Setup", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/users/me/profile-setup", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "stageName", - "value": "Artist One", - "type": "text" - }, - { - "key": "avatarFile", - "type": "file", - "src": [ - - ] - }, - { - "key": "bio", - "value": "Short bio", - "type": "text" - }, - { - "key": "location", - "value": "Riyadh, Saudi Arabia", - "type": "text" - }, - { - "key": "latitude", - "value": "24.7136", - "type": "text" - }, - { - "key": "coverImageFile", - "type": "file", - "src": [ - - ], - "disabled": true - }, - { - "key": "longitude", - "value": "46.6753", - "type": "text" - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.location).to.eql(\u0027Riyadh, Saudi Arabia\u0027);", - "pm.expect(json.latitude).to.eql(24.7136);", - "pm.expect(json.longitude).to.eql(46.6753);", - "if (json.avatar) { pm.expect(json.avatar).to.be.a(\u0027string\u0027); }", - "if (json.coverImage) { pm.expect(json.coverImage).to.be.a(\u0027string\u0027); }" - ] - } - } - ] - }, - { - "name": "Music Setup", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/users/me/music-setup", - "body": { - "mode": "raw", - "raw": "{\n \"musicRoles\": [\"instrumentalist\", \"composer\"],\n \"musicGenres\": [\"Tarab\", \"Pop\"],\n \"experienceLevel\": \"intermediate\",\n \"favoriteInstruments\": [\"Oud\", \"Piano\"],\n \"favoriteMaqamat\": [\"Bayati\", \"Rast\"]\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.musicRoles).to.include(\u0027instrumentalist\u0027);", - "pm.expect(json.musicRoles).to.include(\u0027composer\u0027);", - "pm.expect(json.experienceLevel).to.eql(\u0027intermediate\u0027);", - "pm.expect(json).to.not.have.property(\u0027isInstrumentalist\u0027);", - "pm.expect(json).to.not.have.property(\u0027isSinger\u0027);", - "pm.expect(json).to.not.have.property(\u0027isComposer\u0027);", - "pm.expect(json).to.not.have.property(\u0027isLyricist\u0027);" - ] - } - } - ] - }, - { - "name": "Update Me", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/users/me", - "body": { - "mode": "raw", - "raw": "{\n \"bio\": \"Updated from Postman\",\n \"avatar\": \"https://cdn.example.com/avatar.jpg\",\n \"stageName\": \"Artist One Updated\",\n \"musicGenres\": [\"Tarab\", \"Khaleeji\"],\n \"favoriteInstruments\": [\"Oud\"],\n \"favoriteMaqamat\": [\"Hijaz\"],\n \"location\": \"Jeddah, Saudi Arabia\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.bio).to.eql(\u0027Updated from Postman\u0027);", - "if (json.coverImage) { pm.expect(json.coverImage).to.be.a(\u0027string\u0027); }" - ] - } - } - ] - }, - { - "name": "Update Me With Files", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/users/me", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "stageName", - "value": "Artist One Files", - "type": "text" - }, - { - "key": "bio", - "value": "Updated from Postman with files", - "type": "text" - }, - { - "key": "location", - "value": "Riyadh, Saudi Arabia", - "type": "text" - }, - { - "key": "avatar", - "value": "https://cdn.example.com/avatar.jpg", - "type": "text" - }, - { - "key": "avatarFile", - "type": "file", - "src": [ - - ] - }, - { - "key": "coverImageFile", - "type": "file", - "src": [ - - ], - "disabled": true - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.stageName).to.eql(\u0027Artist One Files\u0027);", - "if (json.avatar) { pm.expect(json.avatar).to.be.a(\u0027string\u0027); }", - "if (json.coverImage) { pm.expect(json.coverImage).to.be.a(\u0027string\u0027); }" - ] - } - } - ] - }, - { - "name": "Get User By Id", - "request": { - "method": "GET", - "url": "{{baseUrl}}/users/{{userId}}", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - } - }, - { - "name": "Discover Talents", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/users/discover?q={{usersQuery}}\u0026musicRole={{talentRole}}\u0026experienceLevel={{talentExperienceLevel}}\u0026hasAvatarOnly={{usersHasAvatar}}\u0026includeRoleBuckets=true\u0026sortBy=followersCount\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=8" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json).to.have.property(\u0027roleBuckets\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027targetUserId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Get Profile Overview", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/users/{{targetUserId}}/profile-overview" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027stats\u0027);", - "pm.expect(json).to.have.property(\u0027contentCounts\u0027);", - "pm.expect(json).to.have.property(\u0027viewerState\u0027);" - ] - } - } - ] - }, - { - "name": "Get User Presence", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/presence", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Presence shape\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027userId\u0027); pm.expect(json).to.have.property(\u0027isOnline\u0027); pm.expect(json).to.have.property(\u0027lastSeenAt\u0027); });" - ] - } - } - }, - { - "name": "Get User Posts By User Id", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/posts?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Paginated user posts\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027items\u0027); pm.expect(json).to.have.property(\u0027pagination\u0027); });" - ] - } - } - } - ] - }, - { - "name": "Posts", - "item": [ - { - "name": "Create Post (Own)", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "First text post with mention @{{targetUsername}} #music", - "type": "text" - }, - { - "key": "taggedUserIds", - "value": "{{targetUserId}}", - "type": "text" - }, - { - "key": "mentionUsernames", - "value": "{{targetUsername}}", - "type": "text" - }, - { - "key": "location", - "value": "Riyadh, Saudi Arabia", - "type": "text" - }, - { - "key": "latitude", - "value": "24.7136", - "type": "text" - }, - { - "key": "longitude", - "value": "46.6753", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027accessToken\u0027)) { throw new Error(\u0027Missing environment var: accessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "const pid = json._id || json.id;", - "pm.environment.set(\u0027postId\u0027, pid);", - "pm.environment.set(\u0027ownPostId\u0027, pid);" - ] - } - } - ] - }, - { - "name": "Create Carousel Post (Instagram Style)", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Weekend jam session #oud #music", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - }, - { - "key": "taggedUserIds", - "value": "{{targetUserId}}", - "type": "text" - }, - { - "key": "mentionUsernames", - "value": "{{targetUsername}}", - "type": "text" - }, - { - "key": "location", - "value": "Riyadh, Saudi Arabia", - "type": "text" - }, - { - "key": "latitude", - "value": "24.7136", - "type": "text" - }, - { - "key": "longitude", - "value": "46.6753", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ] - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ] - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027accessToken\u0027)) { throw new Error(\u0027Missing environment var: accessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.postType).to.eql(\u0027image\u0027);", - "pm.expect(json.processingStatus).to.eql(\u0027ready\u0027);", - "pm.expect(json.media).to.be.an(\u0027object\u0027);", - "pm.expect(json.media.mediaType).to.eql(json.postType);", - "pm.expect(json.imageUrls).to.be.an(\u0027array\u0027);", - "pm.expect(json.media.images).to.be.an(\u0027array\u0027).that.is.not.empty;", - "pm.expect(json.media.images[0].url).to.be.a(\u0027string\u0027);", - "pm.expect(json.imageUrls.length).to.be.greaterThan(0);", - "const pid = json._id || json.id;", - "pm.environment.set(\u0027postId\u0027, pid);", - "pm.environment.set(\u0027ownPostId\u0027, pid);" - ] - } - } - ] - }, - { - "name": "Update Carousel Post (Instagram Style)", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/{{postId}}", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Updated carousel caption #oud #live", - "type": "text" - }, - { - "key": "imageFiles", - "type": "file", - "src": [ - - ] - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.postType).to.eql(\u0027image\u0027);", - "pm.expect(json.imageUrls).to.be.an(\u0027array\u0027);" - ] - } - } - ] - }, - { - "name": "Get User Posts (Own)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/posts/user/{{userId}}?visibility={{postVisibility}}\u0026sortBy={{postSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=10", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027userId\u0027)) missing.push(\u0027userId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (!pm.environment.get(\u0027postId\u0027) \u0026\u0026 json.items \u0026\u0026 json.items.length \u003e 0) {", - " pm.environment.set(\u0027postId\u0027, json.items[0]._id || json.items[0].id);", - " pm.environment.set(\u0027ownPostId\u0027, json.items[0]._id || json.items[0].id);", - "}" - ] - } - } - ] - }, - { - "name": "Get User Posts Filtered", - "request": { - "method": "GET", - "url": "{{baseUrl}}/posts/user/{{userId}}?visibility={{postVisibility}}\u0026postType={{postTypeFilter}}\u0026q={{postSearchQuery}}\u0026hashtag={{postHashtag}}\u0026sortBy={{postSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=10", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Get Post By Id", - "request": { - "method": "GET", - "url": "{{baseUrl}}/posts/{{postId}}", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json._id || json.id).to.exist;", - "pm.expect(json).to.have.property(\u0027content\u0027);" - ] - } - } - ] - }, - { - "name": "Register Post View", - "request": { - "method": "POST", - "url": "{{baseUrl}}/posts/{{postId}}/view", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);", - "pm.expect(json).to.have.property(\u0027viewCount\u0027);", - "pm.environment.set(\u0027lastViewCount\u0027, json.viewCount);" - ] - } - } - ] - }, - { - "name": "Register Post Share", - "request": { - "method": "POST", - "url": "{{baseUrl}}/posts/{{postId}}/share", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);", - "pm.expect(json).to.have.property(\u0027shareCount\u0027);", - "pm.environment.set(\u0027lastShareCount\u0027, json.shareCount);" - ] - } - } - ] - }, - { - "name": "Update Post", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/{{postId}}", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Updated post content with @{{targetUsername}} #live", - "type": "text" - }, - { - "key": "taggedUserIds", - "value": "{{targetUserId}}", - "type": "text" - }, - { - "key": "mentionUsernames", - "value": "{{targetUsername}}", - "type": "text" - }, - { - "key": "location", - "value": "Jeddah, Saudi Arabia", - "type": "text" - }, - { - "key": "latitude", - "value": "21.5433", - "type": "text" - }, - { - "key": "longitude", - "value": "39.1728", - "type": "text" - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.content).to.eql(\u0027Updated post content with @artist_one #live\u0027);" - ] - } - } - ] - }, - { - "name": "Create Video Post (Own)", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Video post #music", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - }, - { - "key": "durationSeconds", - "value": "42", - "type": "text" - }, - { - "key": "coverImageFile", - "type": "file", - "src": [ - - ] - }, - { - "key": "style", - "value": "Sharqi", - "type": "text" - }, - { - "key": "maqam", - "value": "Hijaz", - "type": "text" - }, - { - "key": "mentionUsernames", - "value": "{{targetUsername}}", - "type": "text" - }, - { - "key": "rhythmSignature", - "value": "6/8", - "type": "text" - }, - { - "key": "location", - "value": "Riyadh, Saudi Arabia", - "type": "text" - }, - { - "key": "latitude", - "value": "24.7136", - "type": "text" - }, - { - "key": "longitude", - "value": "46.6753", - "type": "text" - }, - { - "key": "videoFile", - "type": "file", - "src": [ - - ] - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027accessToken\u0027)) { throw new Error(\u0027Missing environment var: accessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.postType).to.eql(\u0027video\u0027);", - "pm.expect(json.media.thumbnailUrl).to.be.a(\u0027string\u0027);", - "pm.expect(json.media.preferredPlaybackUrl).to.be.a(\u0027string\u0027);", - "if (json.hlsUrl) { pm.expect(json.media.preferredPlaybackUrl).to.eql(json.hlsUrl); }", - "pm.expect(json.processingStatus).to.eql(\u0027ready\u0027);", - "pm.expect(json.media).to.be.an(\u0027object\u0027);", - "pm.expect(json.media.mediaType).to.eql(json.postType);", - "pm.expect(json.durationSeconds).to.eql(42);", - "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a(\u0027string\u0027); }", - "pm.expect(json.style).to.eql(\u0027Sharqi\u0027);", - "pm.expect(json.maqam).to.eql(\u0027Hijaz\u0027);", - "pm.expect(json.rhythmSignature).to.eql(\u00276/8\u0027);", - "pm.environment.set(\u0027postId\u0027, json._id || json.id);", - "pm.environment.set(\u0027ownPostId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "Create Reel (Video First)", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/reels", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "New reel from oud session #reel", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - }, - { - "key": "durationSeconds", - "value": "30", - "type": "text" - }, - { - "key": "coverImageFile", - "type": "file", - "src": [ - - ] - }, - { - "key": "style", - "value": "Sharqi", - "type": "text" - }, - { - "key": "maqam", - "value": "Hijaz", - "type": "text" - }, - { - "key": "rhythmSignature", - "value": "6/8", - "type": "text" - }, - { - "key": "videoFile", - "type": "file", - "src": [ - - ] - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027accessToken\u0027)) { throw new Error(\u0027Missing environment var: accessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.postType).to.eql(\u0027video\u0027);", - "pm.expect(json.media.thumbnailUrl).to.be.a(\u0027string\u0027);", - "pm.expect(json.media.preferredPlaybackUrl).to.be.a(\u0027string\u0027);", - "if (json.hlsUrl) { pm.expect(json.media.preferredPlaybackUrl).to.eql(json.hlsUrl); }", - "pm.expect(json.processingStatus).to.eql(\u0027ready\u0027);", - "pm.expect(json.media).to.be.an(\u0027object\u0027);", - "pm.expect(json.media.mediaType).to.eql(json.postType);", - "pm.expect(json.durationSeconds).to.eql(30);", - "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a(\u0027string\u0027); }", - "pm.expect(json.style).to.eql(\u0027Sharqi\u0027);", - "pm.expect(json.maqam).to.eql(\u0027Hijaz\u0027);", - "pm.expect(json.rhythmSignature).to.eql(\u00276/8\u0027);", - "pm.environment.set(\u0027postId\u0027, json._id || json.id);", - "pm.environment.set(\u0027ownPostId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "Get Reels", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/reels?visibility={{postVisibility}}\u0026authorId={{userId}}\u0026sortBy={{postSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=10" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Get Reels Filtered", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/reels?visibility={{postVisibility}}\u0026authorId={{userId}}\u0026q={{reelQuery}}\u0026sortBy={{postSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=10" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Create Audio Post (Own)", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Audio post #oud", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - }, - { - "key": "durationSeconds", - "value": "54", - "type": "text" - }, - { - "key": "coverImageFile", - "type": "file", - "src": [ - - ] - }, - { - "key": "style", - "value": "Sharqi", - "type": "text" - }, - { - "key": "maqam", - "value": "Hijaz", - "type": "text" - }, - { - "key": "rhythmSignature", - "value": "6/8", - "type": "text" - }, - { - "key": "audioFile", - "type": "file", - "src": [ - - ] - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027accessToken\u0027)) { throw new Error(\u0027Missing environment var: accessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.postType).to.eql(\u0027audio\u0027);", - "pm.expect(json.media.audioUrl).to.be.a(\u0027string\u0027);", - "pm.expect(json.media.waveformPeaks).to.be.an(\u0027array\u0027);", - "pm.expect(json.media.durationSeconds).to.exist;", - "pm.expect(json.processingStatus).to.eql(\u0027ready\u0027);", - "pm.expect(json.media).to.be.an(\u0027object\u0027);", - "pm.expect(json.media.mediaType).to.eql(json.postType);", - "pm.expect(json.durationSeconds).to.eql(54);", - "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a(\u0027string\u0027); }", - "pm.expect(json.style).to.eql(\u0027Sharqi\u0027);", - "pm.expect(json.maqam).to.eql(\u0027Hijaz\u0027);", - "pm.expect(json.rhythmSignature).to.eql(\u00276/8\u0027);", - "pm.environment.set(\u0027postId\u0027, json._id || json.id);", - "pm.environment.set(\u0027ownPostId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "Create Audio Post (URL + Waveform)", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Audio post with waveform #oud #hijaz", - "type": "text" - }, - { - "key": "audioUrl", - "value": "https://cdn.example.com/audio-sample.mp3", - "type": "text" - }, - { - "key": "durationSeconds", - "value": "54", - "type": "text" - }, - { - "key": "thumbnailUrl", - "value": "https://cdn.example.com/audio-cover.jpg", - "type": "text" - }, - { - "key": "style", - "value": "Sharqi", - "type": "text" - }, - { - "key": "maqam", - "value": "Hijaz", - "type": "text" - }, - { - "key": "rhythmSignature", - "value": "6/8", - "type": "text" - }, - { - "key": "waveformPeaks", - "value": "[12,38,27,49,22,44]", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027accessToken\u0027)) { throw new Error(\u0027Missing environment var: accessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.postType).to.eql(\u0027audio\u0027);", - "pm.expect(json.media.audioUrl).to.be.a(\u0027string\u0027);", - "pm.expect(json.media.waveformPeaks).to.be.an(\u0027array\u0027);", - "pm.expect(json.media.durationSeconds).to.exist;", - "pm.expect(json.processingStatus).to.eql(\u0027ready\u0027);", - "pm.expect(json.media).to.be.an(\u0027object\u0027);", - "pm.expect(json.media.mediaType).to.eql(json.postType);", - "pm.expect(json.waveformPeaks).to.be.an(\u0027array\u0027);", - "pm.expect(json.waveformPeaks.length).to.eql(6);", - "pm.expect(json.maqam).to.eql(\u0027Hijaz\u0027);", - "pm.environment.set(\u0027postId\u0027, json._id || json.id);", - "pm.environment.set(\u0027ownPostId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "Register Post Play (Audio/Video)", - "request": { - "method": "POST", - "url": "{{baseUrl}}/posts/{{postId}}/play", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);", - "pm.expect(json).to.have.property(\u0027playCount\u0027);", - "pm.environment.set(\u0027lastPlayCount\u0027, json.playCount);" - ] - } - } - ] - }, - { - "name": "Create Invalid Post (Video+Audio)", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Invalid post", - "type": "text" - }, - { - "key": "videoUrl", - "value": "https://cdn.example.com/video.mp4", - "type": "text" - }, - { - "key": "audioUrl", - "value": "https://cdn.example.com/audio.mp3", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027accessToken\u0027)) { throw new Error(\u0027Missing environment var: accessToken\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 400\u0027, function () { pm.response.to.have.status(400); });" - ] - } - } - ] - }, - { - "name": "Update Post Video (Form-Data)", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/{{postId}}", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Updated with video file", - "type": "text" - }, - { - "key": "durationSeconds", - "value": "58", - "type": "text" - }, - { - "key": "coverImageFile", - "type": "file", - "src": [ - - ] - }, - { - "key": "style", - "value": "Contemporary", - "type": "text" - }, - { - "key": "maqam", - "value": "Bayati", - "type": "text" - }, - { - "key": "rhythmSignature", - "value": "4/4", - "type": "text" - }, - { - "key": "videoFile", - "type": "file", - "src": [ - - ] - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.postType).to.eql(\u0027video\u0027);", - "pm.expect(json.durationSeconds).to.eql(58);", - "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a(\u0027string\u0027); }", - "pm.expect(json.style).to.eql(\u0027Contemporary\u0027);", - "pm.expect(json.maqam).to.eql(\u0027Bayati\u0027);", - "pm.expect(json.rhythmSignature).to.eql(\u00274/4\u0027);" - ] - } - } - ] - }, - { - "name": "Update Post Audio (Form-Data)", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/{{postId}}", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Updated with audio file", - "type": "text" - }, - { - "key": "durationSeconds", - "value": "61", - "type": "text" - }, - { - "key": "coverImageFile", - "type": "file", - "src": [ - - ] - }, - { - "key": "style", - "value": "Tarab", - "type": "text" - }, - { - "key": "maqam", - "value": "Nahawand", - "type": "text" - }, - { - "key": "rhythmSignature", - "value": "6/8", - "type": "text" - }, - { - "key": "audioFile", - "type": "file", - "src": [ - - ] - } - ] - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.postType).to.eql(\u0027audio\u0027);", - "pm.expect(json.durationSeconds).to.eql(61);", - "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a(\u0027string\u0027); }", - "pm.expect(json.style).to.eql(\u0027Tarab\u0027);", - "pm.expect(json.maqam).to.eql(\u0027Nahawand\u0027);", - "pm.expect(json.rhythmSignature).to.eql(\u00276/8\u0027);" - ] - } - } - ] - }, - { - "name": "Delete Post (Cleanup - Run Last)", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/{{postId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.success).to.eql(true);" - ] - } - } - ] - }, - { - "name": "Repost Post", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/posts/{{postId}}/repost", - "body": { - "mode": "raw", - "raw": "{\n \"content\": \"\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027repostId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "Quote Post", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/posts/{{postId}}/repost", - "body": { - "mode": "raw", - "raw": "{\n \"content\": \"My take on this post\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027quotePostId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "Update Post Comment Settings", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/{{postId}}/comment-settings", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "{{postId}}", - "comment-settings" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"commentsDisabled\": false,\n \"commentsFollowersOnly\": true,\n \"commentFilterKeywords\": [\n \"{{commentKeyword}}\",\n \"offensive\"\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Pin Post To Profile", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/{{postId}}/pin-profile", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "{{postId}}", - "pin-profile" - ], - "query": [ - - ] - } - } - }, - { - "name": "Unpin Post From Profile", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/{{postId}}/unpin-profile", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "{{postId}}", - "unpin-profile" - ], - "query": [ - - ] - } - } - }, - { - "name": "Archive Post", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/{{postId}}/archive", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "{{postId}}", - "archive" - ], - "query": [ - - ] - } - } - }, - { - "name": "Restore Archived Post", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/{{postId}}/restore-archive", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "{{postId}}", - "restore-archive" - ], - "query": [ - - ] - } - } - }, - { - "name": "Create Carousel Post With Metadata", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/posts", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts" - ], - "query": [ - - ] - }, - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Carousel caption #oud", - "type": "text" - }, - { - "key": "imageUrls", - "value": "https://cdn.example.com/1.jpg,https://cdn.example.com/2.jpg", - "type": "text" - }, - { - "key": "imageCaptions", - "value": "First image,Second image", - "type": "text" - }, - { - "key": "imageAltTexts", - "value": "Oud on stage,Audience closeup", - "type": "text" - }, - { - "key": "collaboratorIds", - "value": "{{collaboratorId}}", - "type": "text" - }, - { - "key": "commentsDisabled", - "value": "false", - "type": "text" - }, - { - "key": "commentsFollowersOnly", - "value": "false", - "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 value = json.item;", - "if (value) pm.environment.set(\"postId\", value._id || value.id || value);" - ] - } - } - ] - }, - { - "name": "Update Carousel Alt Text", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/{{postId}}", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "{{postId}}" - ], - "query": [ - - ] - }, - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "imageUrls", - "value": "https://cdn.example.com/1.jpg,https://cdn.example.com/2.jpg", - "type": "text" - }, - { - "key": "imageCaptions", - "value": "Updated first,Updated second", - "type": "text" - }, - { - "key": "imageAltTexts", - "value": "Updated oud alt,Updated audience alt", - "type": "text" - } - ] - } - } - }, - { - "name": "Disable Comments On Post", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/{{postId}}/comment-settings", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "{{postId}}", - "comment-settings" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"commentsDisabled\": true,\n \"commentsFollowersOnly\": false,\n \"commentFilterKeywords\": []\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Enable Comments On Post", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/posts/{{postId}}/comment-settings", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "posts", - "{{postId}}", - "comment-settings" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"commentsDisabled\": false,\n \"commentsFollowersOnly\": false,\n \"commentFilterKeywords\": []\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - } - ] - }, - { - "name": "Comments", - "item": [ - { - "name": "Create Comment", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/comments", - "body": { - "mode": "raw", - "raw": "{\n \"postId\": \"{{postId}}\",\n \"content\": \"Awesome post!\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "const cid = json._id || json.id;", - "pm.environment.set(\u0027commentId\u0027, cid);", - "pm.environment.set(\u0027ownCommentId\u0027, cid);" - ] - } - } - ] - }, - { - "name": "Create Comment With Mention", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/comments", - "body": { - "mode": "raw", - "raw": "{\n \"postId\": \"{{postId}}\",\n \"content\": \"Awesome post @{{targetUsername}}!\",\n \"mentionUsernames\": [\"{{targetUsername}}\"]\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (!pm.environment.get(\u0027targetUsername\u0027)) missing.push(\u0027targetUsername\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "const cid = json._id || json.id;", - "pm.environment.set(\u0027commentId\u0027, cid);", - "pm.expect(json.mentionUsernames || []).to.include(pm.environment.get(\u0027targetUsername\u0027).toLowerCase());" - ] - } - } - ] - }, - { - "name": "Get Post Comments", - "request": { - "method": "GET", - "url": "{{baseUrl}}/comments/post/{{postId}}?page=1\u0026limit=10\u0026sortBy={{commentSortBy}}\u0026sortOrder={{listSortOrder}}", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Create Comment Reply", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/comments/{{commentId}}/replies", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "comments", - "{{commentId}}", - "replies" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"content\": \"Reply to comment\"\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();", - "const value = json.item;", - "if (value) pm.environment.set(\"replyCommentId\", value._id || value.id || value);" - ] - } - } - ] - }, - { - "name": "Get Comment Replies", - "request": { - "method": "GET", - "url": "{{baseUrl}}/comments/{{commentId}}/replies?page=1\u0026limit=10\u0026sortBy={{commentSortBy}}\u0026sortOrder={{listSortOrder}}", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Update Comment", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/comments/{{commentId}}", - "body": { - "mode": "raw", - "raw": "{\n \"content\": \"Edited comment @{{secondaryUsername}}\",\n \"mentionUsernames\": [\"{{secondaryUsername}}\"]\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.content).to.include(\u0027Edited comment\u0027);" - ] - } - } - ] - }, - { - "name": "Delete Comment", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/comments/{{commentId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Pin Comment", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/comments/{{commentId}}/pin", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "comments", - "{{commentId}}", - "pin" - ], - "query": [ - - ] - } - } - }, - { - "name": "Unpin Comment", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/comments/{{commentId}}/unpin", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "comments", - "{{commentId}}", - "unpin" - ], - "query": [ - - ] - } - } - }, - { - "name": "Create Comment Hidden By Filter", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/comments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "comments" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"postId\": \"{{postId}}\",\n \"content\": \"This contains {{commentKeyword}} keyword\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Get Post Comments - Top", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/comments/post/{{postId}}?page=1\u0026limit=20\u0026sortBy=top\u0026sortOrder=desc", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "comments", - "post", - "{{postId}}" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - }, - { - "key": "sortBy", - "value": "top" - }, - { - "key": "sortOrder", - "value": "desc" - } - ] - } - } - }, - { - "name": "Get Post Comments - Newest", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/comments/post/{{postId}}?page=1\u0026limit=20\u0026sortBy=createdAt\u0026sortOrder=desc", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "comments", - "post", - "{{postId}}" - ], - "query": [ - { - "key": "page", - "value": "1" - }, - { - "key": "limit", - "value": "20" - }, - { - "key": "sortBy", - "value": "createdAt" - }, - { - "key": "sortOrder", - "value": "desc" - } - ] - } - } - } - ] - }, - { - "name": "Likes", - "item": [ - { - "name": "Like Post (Set liked=true)", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes", - "body": { - "mode": "raw", - "raw": "{\n \"targetId\": \"{{postId}}\",\n \"targetType\": \"post\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.liked).to.eql(true);" - ] - } - } - ] - }, - { - "name": "Unlike Post (Set liked=false)", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes/post/{{postId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.liked).to.eql(false);" - ] - } - } - ] - }, - { - "name": "Get Post Like Status", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes/status/post/{{postId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027liked\u0027);" - ] - } - } - ] - }, - { - "name": "Like Comment", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes", - "body": { - "mode": "raw", - "raw": "{\n \"targetId\": \"{{commentId}}\",\n \"targetType\": \"comment\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.liked).to.eql(true);" - ] - } - } - ] - }, - { - "name": "Unlike Comment", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes/comment/{{commentId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.liked).to.eql(false);" - ] - } - } - ] - }, - { - "name": "Get Comment Like Status", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes/status/comment/{{commentId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027liked\u0027);" - ] - } - } - ] - }, - { - "name": "React To Post", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes", - "body": { - "mode": "raw", - "raw": "{\n \"targetId\": \"{{postId}}\",\n \"targetType\": \"post\",\n \"reactionType\": \"{{reactionType}}\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.reactionType).to.exist;" - ] - } - } - ] - }, - { - "name": "Reaction Status - Post", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes/status/post/{{postId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027reactionSummary\u0027);" - ] - } - } - ] - }, - { - "name": "Toggle Like", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/likes/toggle", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "likes", - "toggle" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"targetType\": \"post\",\n \"targetId\": \"{{postId}}\",\n \"reactionType\": \"like\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - } - ] - }, - { - "name": "Saves", - "item": [ - { - "name": "Save Post (Set saved=true)", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/saves", - "body": { - "mode": "raw", - "raw": "{\n \"postId\": \"{{postId}}\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027postId\u0027)) missing.push(\u0027postId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.saved).to.eql(true);" - ] - } - } - ] - }, - { - "name": "Unsave Post (Set saved=false)", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/saves/{{postId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.saved).to.eql(false);" - ] - } - } - ] - }, - { - "name": "Get Save Status", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/saves/status/{{postId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027saved\u0027);" - ] - } - } - ] - }, - { - "name": "Get My Saved Posts", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/saves/me?page=1\u0026limit=10\u0026sortOrder={{listSortOrder}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Toggle Save", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/saves/toggle", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "saves", - "toggle" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"postId\": \"{{postId}}\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - } - ] - }, - { - "name": "Follows", - "item": [ - { - "name": "Follow User (Instagram Style)", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{targetUserId}}/follow", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Follow response\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027isFollowing\u0027); pm.expect(json).to.have.property(\u0027targetUserId\u0027); });" - ] - } - } - }, - { - "name": "Unfollow User (Instagram Style)", - "request": { - "method": "DELETE", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{targetUserId}}/follow", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Unfollow response\u0027, function () { const json = pm.response.json(); pm.expect(json.isFollowing).to.eql(false); });" - ] - } - } - }, - { - "name": "Get User Follow Status", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{targetUserId}}/follow-status", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Follow status shape\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027isFollowing\u0027); pm.expect(json).to.have.property(\u0027isFollowedBy\u0027); pm.expect(json).to.have.property(\u0027isMutual\u0027); });" - ] - } - } - }, - { - "name": "Get User Followers", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/followers?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Followers list shape\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027items\u0027); pm.expect(json).to.have.property(\u0027pagination\u0027); });" - ] - } - } - }, - { - "name": "Get User Following", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/{{userId}}/following?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Following list shape\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027items\u0027); pm.expect(json).to.have.property(\u0027pagination\u0027); });" - ] - } - } - }, - { - "name": "Get My Followers", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/me/followers?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Get My Following", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/users/me/following?page=1\u0026limit=20\u0026sortOrder={{listSortOrder}}", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Toggle Follow User", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/follows/toggle", - "body": { - "mode": "raw", - "raw": "{\n \"targetUserId\": \"{{targetUserId}}\"\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027targetUserId\u0027)) missing.push(\u0027targetUserId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027following\u0027);", - "pm.environment.set(\u0027lastFollowingState\u0027, json.following ? \u0027following\u0027 : \u0027not_following\u0027);" - ] - } - } - ] - }, - { - "name": "Get Followers", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/follows/followers/{{userId}}?page=1\u0026limit=10\u0026sortOrder={{listSortOrder}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Get Following", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/follows/following/{{userId}}?page=1\u0026limit=10\u0026sortOrder={{listSortOrder}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Get Follow Status", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/follows/status/{{targetUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027following\u0027);" - ] - } - } - ] - }, - { - "name": "Get Follow Suggestions", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/follows/suggestions?page=1\u0026limit=10\u0026sortOrder={{listSortOrder}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "List Follow Requests", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/follows/requests?page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "if (json.items \u0026\u0026 json.items.length) { pm.environment.set(\u0027followRequestId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Approve Follow Request", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/follows/requests/{{followRequestId}}/approve" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Reject Follow Request", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/follows/requests/{{followRequestId}}/reject" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - } - ] - }, - { - "name": "Notifications", - "item": [ - { - "name": "Get My Notifications", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/notifications?read={{notificationRead}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) {", - " pm.environment.set(\u0027notificationId\u0027, json.items[0]._id || json.items[0].id);", - " pm.expect(json.items[0]).to.have.property(\u0027title\u0027);", - " pm.expect(json.items[0]).to.have.property(\u0027resourceType\u0027);", - " pm.expect(json.items[0]).to.have.property(\u0027deepLink\u0027);", - "}" - ] - } - } - ] - }, - { - "name": "Get My Notifications Filtered", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/notifications?read={{notificationRead}}\u0026type={{notificationType}}\u0026resourceType={{notificationResourceType}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;" - ] - } - } - ] - }, - { - "name": "Get Unread Notifications Count", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/notifications/unread-count" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027unreadCount\u0027);" - ] - } - } - ] - }, - { - "name": "Mark Notification Read", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/notifications/{{notificationId}}/read" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027unreadCount\u0027);" - ] - } - } - ] - }, - { - "name": "Mark All Notifications Read", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/notifications/read-all" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.unreadCount).to.eql(0);" - ] - } - } - ] - } - ] - }, - { - "name": "Chat", - "item": [ - { - "name": "Create Conversation", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/conversations", - "body": { - "mode": "raw", - "raw": "{\n \"participantIds\": [\"{{targetUserId}}\"],\n \"isGroup\": false\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "const missing = [];", - "if (!pm.environment.get(\u0027accessToken\u0027)) missing.push(\u0027accessToken\u0027);", - "if (!pm.environment.get(\u0027targetUserId\u0027)) missing.push(\u0027targetUserId\u0027);", - "if (missing.length) { throw new Error(\u0027Missing environment vars: \u0027 + missing.join(\u0027, \u0027)); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.environment.set(\u0027conversationId\u0027, json._id || json.id);" - ] - } - } - ] - }, - { - "name": "Get My Conversations", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/conversations?limit=20\u0026cursor={{chatConversationsCursor}}\u0026sortOrder={{listSortOrder}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027conversationId\u0027, json.items[0]._id || json.items[0].id); }", - "pm.environment.set(\u0027chatConversationsCursor\u0027, json.nextCursor || \u0027\u0027);" - ] - } - } - ] - }, - { - "name": "Send Message", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/chat/messages", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "chat", - "messages" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"conversationId\": \"{{conversationId}}\",\n \"content\": \"Hello from Postman\"\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();", - "const value = json.item;", - "if (value) pm.environment.set(\"messageId\", value._id || value.id || value);" - ] - } - } - ] - }, - { - "name": "Get Conversation Messages", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/conversations/{{conversationId}}/messages?limit=20\u0026cursor={{chatMessagesCursor}}\u0026sortOrder={{listSortOrder}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.pagination).to.exist;", - "pm.environment.set(\u0027chatMessagesCursor\u0027, json.nextCursor || \u0027\u0027);" - ] - } - } - ] - }, - { - "name": "Mark Message Seen", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/messages/{{messageId}}/seen" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Unsend Message", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/messages/{{messageId}}/unsend" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Block User In Chat", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/blocks/{{targetUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.blocked).to.eql(true);" - ] - } - } - ] - }, - { - "name": "Unblock User In Chat", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/blocks/{{targetUserId}}/unblock" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Get Chat Block Status", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/blocks/status/{{targetUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027iBlocked\u0027);", - "pm.expect(json).to.have.property(\u0027blockedMe\u0027);" - ] - } - } - ] - }, - { - "name": "Get My Chat Blocks", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/chat/blocks" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);" - ] - } - } - ] - }, - { - "name": "Send Reply Message", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/chat/messages", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "chat", - "messages" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"conversationId\": \"{{conversationId}}\",\n \"content\": \"Replying to a message\",\n \"replyToMessageId\": \"{{replyToMessageId}}\"\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();", - "const value = json.item;", - "if (value) pm.environment.set(\"messageId\", value._id || value.id || value);" - ] - } - } - ] - }, - { - "name": "Send Message Media Upload", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": { - "raw": "{{baseUrl}}/chat/messages/upload", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "chat", - "messages", - "upload" - ], - "query": [ - - ] - }, - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "conversationId", - "value": "{{conversationId}}", - "type": "text" - }, - { - "key": "content", - "value": "Media message caption", - "type": "text" - }, - { - "key": "replyToMessageId", - "value": "{{replyToMessageId}}", - "type": "text" - }, - { - "key": "mediaFile", - "type": "file", - "src": "{{mediaFilePath}}" - } - ] - } - }, - "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 value = json.item;", - "if (value) pm.environment.set(\"messageId\", value._id || value.id || value);" - ] - } - } - ] - }, - { - "name": "React To Message", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/chat/messages/{{messageId}}/reaction", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "chat", - "messages", - "{{messageId}}", - "reaction" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"reactionType\": \"love\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Delete Message For Me", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/chat/messages/{{messageId}}/delete-for-me", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "chat", - "messages", - "{{messageId}}", - "delete-for-me" - ], - "query": [ - - ] - } - } - } - ] - }, - { - "name": "Feed", - "item": [ - { - "name": "Get My Feed Mixed Home", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/feed/me?page=1\u0026limit=20\u0026includeSuggestions={{feedIncludeSuggestions}}\u0026suggestionInterval={{feedSuggestionInterval}}\u0026followingOnly={{feedFollowingOnly}}\u0026radiusKm={{feedRadiusKm}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "if (json.items \u0026\u0026 json.items.length \u003e 0) {", - " pm.expect(json.items[0]).to.have.property(\u0027feedItemType\u0027);", - "}", - "const hasMixedItems = (json.items || []).some(item =\u003e item.feedItemType === \u0027suggested_users\u0027 || item.feedItemType === \u0027featured_marketplace\u0027);", - "pm.environment.set(\u0027homeFeedHasMixedItems\u0027, hasMixedItems ? \u0027true\u0027 : \u0027false\u0027);" - ] - } - } - ] - }, - { - "name": "Get My Feed", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/feed/me?limit=20\u0026cursor={{feedCursor}}\u0026followingOnly={{feedFollowingOnly}}\u0026radiusKm={{feedRadiusKm}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "const firstPost = (json.items || []).find(item =\u003e item.feedItemType === \u0027post\u0027);", - "if (firstPost) {", - " pm.expect(firstPost).to.have.property(\u0027likedByMe\u0027);", - " pm.expect(firstPost).to.have.property(\u0027savedByMe\u0027);", - " pm.expect(firstPost).to.have.property(\u0027engagement\u0027);", - "}", - "pm.environment.set(\u0027feedCursor\u0027, json.nextCursor || \u0027\u0027);" - ] - } - } - ] - }, - { - "name": "Get My Feed Preferred Type", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/feed/me?limit=20\u0026cursor={{feedCursor}}\u0026preferredPostType={{feedPreferredPostType}}\u0026followingOnly={{feedFollowingOnly}}\u0026radiusKm={{feedRadiusKm}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.pagination).to.exist;", - "const firstPost = (json.items || []).find(item =\u003e item.feedItemType === \u0027post\u0027);", - "if (firstPost) { pm.expect(firstPost).to.have.property(\u0027engagement\u0027); }", - "pm.environment.set(\u0027feedCursor\u0027, json.nextCursor || \u0027\u0027);" - ] - } - } - ] - }, - { - "name": "Get Trending Feed", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/feed/trending?limit=20\u0026cursor={{feedCursor}}\u0026preferredPostType={{feedPreferredPostType}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "const firstPost = (json.items || []).find(item =\u003e item.feedItemType === \u0027post\u0027);", - "if (firstPost) {", - " pm.expect(firstPost).to.have.property(\u0027likedByMe\u0027);", - " pm.expect(firstPost).to.have.property(\u0027savedByMe\u0027);", - "}", - "pm.environment.set(\u0027feedCursor\u0027, json.nextCursor || \u0027\u0027);" - ] - } - } - ] - }, - { - "name": "Explore Feed", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/feed/explore?page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);" - ] - } - } - ] - } - ] - }, - { - "name": "Smoke", - "item": [ - { - "name": "Login (Smoke)", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": "{{baseUrl}}/auth/login", - "body": { - "mode": "raw", - "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\"\n}" - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "const uid = json.user._id || json.user.id;", - "pm.environment.set(\u0027accessToken\u0027, json.accessToken);", - "pm.environment.set(\u0027refreshToken\u0027, json.refreshToken);", - "pm.environment.set(\u0027userId\u0027, uid);" - ] - } - } - ] - }, - { - "name": "Get User Posts (Smoke)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/posts/user/{{userId}}?page=1\u0026limit=10", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ] - } - }, - { - "name": "Get My Feed (Smoke)", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/feed/me?limit=20\u0026cursor={{feedCursor}}\u0026radiusKm=30" - } - } - ] - }, - { - "name": "Full E2E", - "item": [ - { - "name": "Create Post (Own) - E2E", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "E2E text post", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - } - ] - } - } - }, - { - "name": "Create Comment - E2E", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/comments", - "body": { - "mode": "raw", - "raw": "{\n \"postId\": \"{{postId}}\",\n \"content\": \"E2E comment\"\n}" - } - } - }, - { - "name": "Like Post - E2E", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/likes", - "body": { - "mode": "raw", - "raw": "{\n \"targetId\": \"{{postId}}\",\n \"targetType\": \"post\"\n}" - } - } - }, - { - "name": "Save Post - E2E", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/saves", - "body": { - "mode": "raw", - "raw": "{\n \"postId\": \"{{postId}}\"\n}" - } - } - }, - { - "name": "Delete Post - E2E Cleanup", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/{{postId}}" - } - } - ] - }, - { - "name": "Negative Tests", - "item": [ - { - "name": "Create Invalid Post (Video+Audio) - Negative", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "Invalid post", - "type": "text" - }, - { - "key": "videoUrl", - "value": "https://cdn.example.com/video.mp4", - "type": "text" - }, - { - "key": "audioUrl", - "value": "https://cdn.example.com/audio.mp3", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 400\u0027, function () { pm.response.to.have.status(400); });" - ] - } - } - ] - }, - { - "name": "Create Post Without Token - Negative", - "request": { - "method": "POST", - "header": [ - - ], - "url": "{{baseUrl}}/posts", - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "content", - "value": "No token request", - "type": "text" - }, - { - "key": "visibility", - "value": "public", - "type": "text" - } - ] - } - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 401\u0027, function () { pm.response.to.have.status(401); });" - ] - } - } - ] - } - ] - }, - { - "name": "Cleanup", - "item": [ - { - "name": "Delete Own Comment (if exists)", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/comments/{{ownCommentId}}" - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027ownCommentId\u0027)) { postman.setNextRequest(\u0027Delete Own Post (if exists)\u0027); }" - ] - } - } - ] - }, - { - "name": "Delete Own Post (if exists)", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/posts/{{ownPostId}}" - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027ownPostId\u0027)) { postman.setNextRequest(\u0027Delete Instrument (if exists)\u0027); }" - ] - } - } - ] - } - ] - }, - { - "name": "AI Music", - "item": [ - { - "name": "Generate Music From Text (Gemini)", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/media/ai/text-to-music", - "body": { - "mode": "raw", - "raw": "{\n \"prompt\": \"{{aiMusicPrompt}}\",\n \"durationSeconds\": {{aiMusicDuration}},\n \"seed\": {{aiMusicSeed}}\n}" - } - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "if (!pm.environment.get(\u0027aiMusicPrompt\u0027)) { pm.environment.set(\u0027aiMusicPrompt\u0027, \u0027Calm oud melody with light percussion and emotional Arabic mood\u0027); }", - "if (!pm.environment.get(\u0027aiMusicDuration\u0027)) { pm.environment.set(\u0027aiMusicDuration\u0027, \u002712\u0027); }", - "if (!pm.environment.get(\u0027aiMusicSeed\u0027)) { pm.environment.set(\u0027aiMusicSeed\u0027, \u002742\u0027); }" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });", - "const json = pm.response.json();", - "pm.expect(json.audioUrl).to.exist;", - "pm.expect(json.mimeType).to.exist;", - "pm.expect(json.sizeBytes).to.be.a(\u0027number\u0027);", - "pm.environment.set(\u0027generatedMusicUrl\u0027, json.audioUrl);" - ] - } - } - ] - } - ] - }, - { - "name": "Marketplace", - "item": [ - { - "name": "01 Marketplace Overview", - "item": [ - { - "name": "Public Marketplace Home", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/marketplace/home?listingsLimit=4\u0026instrumentsLimit=4\u0026repairShopsLimit=3\u0026onlyActive=true" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json).to.have.property(\u0027categories\u0027);", - "pm.expect(json).to.have.property(\u0027summary\u0027);", - "pm.expect(json).to.have.property(\u0027featuredShops\u0027);", - "pm.expect(json.filters).to.have.property(\u0027listingCategories\u0027);", - "pm.expect(json.sections).to.have.property(\u0027listings\u0027);", - "pm.expect(json.sections).to.have.property(\u0027musicalInstruments\u0027);", - "pm.expect(json.sections).to.have.property(\u0027repairShops\u0027);" - ] - } - } - ] - } - ] - }, - { - "name": "03 Repair Shops", - "item": [ - { - "name": "Public", - "item": [ - { - "name": "Public List Repair Shops", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/marketplace/repair-shops?q={{repairShopQuery}}\u0026isActive={{repairShopIsActive}}\u0026sortBy={{repairShopSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027repairShopId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Public Get Repair Shop By Id", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/marketplace/repair-shops/{{repairShopId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - } - ] - } - ] - }, - { - "name": "04 Musical Instruments", - "item": [ - { - "name": "Public", - "item": [ - { - "name": "Public List Instruments", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/marketplace/instruments?q={{marketplaceQuery}}\u0026minPrice={{marketplaceMinPrice}}\u0026maxPrice={{marketplaceMaxPrice}}\u0026isActive={{marketplaceIsActive}}\u0026condition={{marketplaceCondition}}\u0026instrumentType={{marketplaceInstrumentType}}\u0026sortBy={{marketplaceSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027instrumentId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Public Get Instrument By Id", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/marketplace/instruments/{{instrumentId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - } - ] - } - ] - }, - { - "name": "05 General Marketplace", - "item": [ - { - "name": "Public", - "item": [ - { - "name": "Public List Listings", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/marketplace/listings?q={{marketplaceQuery}}\u0026minPrice={{marketplaceMinPrice}}\u0026maxPrice={{marketplaceMaxPrice}}\u0026isActive={{marketplaceIsActive}}\u0026listingCategory={{marketplaceListingCategory}}\u0026condition={{marketplaceCondition}}\u0026instrumentType={{marketplaceInstrumentType}}\u0026sortBy={{marketplaceSortBy}}\u0026sortOrder={{listSortOrder}}\u0026page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });", - "const json = pm.response.json();", - "pm.expect(json.items).to.be.an(\u0027array\u0027);", - "pm.expect(json.pagination).to.exist;", - "if (json.items \u0026\u0026 json.items.length \u003e 0) { pm.environment.set(\u0027listingId\u0027, json.items[0]._id || json.items[0].id); }" - ] - } - } - ] - }, - { - "name": "Public Get Listing By Id", - "request": { - "method": "GET", - "header": [ - - ], - "url": "{{baseUrl}}/marketplace/listings/{{listingId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - } - ] - } - ] - } - ] - }, - { - "name": "Blocks", - "item": [ - { - "name": "Block User", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/blocks/{{targetUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 201\u0027, function () { pm.response.to.have.status(201); });" - ] - } - } - ] - }, - { - "name": "Block Status", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/blocks/status/{{targetUserId}}" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Unblock User", - "request": { - "method": "PATCH", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/blocks/{{targetUserId}}/unblock" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - } - ] - }, - { - "name": "Reports", - "item": [ - { - "name": "Report Post", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/reports", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "reports" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"targetType\": \"post\",\n \"targetId\": \"{{postId}}\",\n \"reason\": \"spam\",\n \"details\": \"This content looks suspicious\"\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();", - "const value = json.item;", - "if (value) pm.environment.set(\"reportId\", value._id || value.id || value);" - ] - } - } - ] - }, - { - "name": "My Reports", - "request": { - "method": "GET", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - } - ], - "url": "{{baseUrl}}/reports/me?page=1\u0026limit=20" - }, - "event": [ - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is 200\u0027, function () { pm.response.to.have.status(200); });" - ] - } - } - ] - }, - { - "name": "Report Post - Fixed Reason", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/reports", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "reports" - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"targetType\": \"post\",\n \"targetId\": \"{{postId}}\",\n \"reason\": \"{{reportReason}}\",\n \"details\": \"Reported from Postman\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Report User And Block", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/reports", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "reports" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"targetType\": \"user\",\n \"targetId\": \"{{targetUserId}}\",\n \"reason\": \"harassment\",\n \"details\": \"Unsafe interaction\",\n \"blockTarget\": true\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();", - "const value = json.item;", - "if (value) pm.environment.set(\"reportId\", value._id || value.id || value);" - ] - } - } - ] - }, - { - "name": "Report Comment", - "request": { - "method": "POST", - "header": [ - { - "key": "Authorization", - "value": "Bearer {{accessToken}}" - }, - { - "key": "Content-Type", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/reports", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "reports" - ], - "query": [ - - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"targetType\": \"comment\",\n \"targetId\": \"{{commentId}}\",\n \"reason\": \"harassment\",\n \"details\": \"Offensive comment\"\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();", - "const value = json.item;", - "if (value) pm.environment.set(\"reportId\", value._id || value.id || value);" - ] - } - } - ] - } - ] - }, - { - "name": "Health", - "item": [ - { - "name": "Health Check", - "request": { - "method": "GET", - "header": [ - - ], - "url": { - "raw": "{{baseUrl}}/", - "host": [ - "{{baseUrl}}" - ], - "path": [ - - ] - } - } - } - ] - }, - { - "name": "Devices", - "item": [ - { - "name": "Register Device FCM Token", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/devices/register", - "body": { - "mode": "raw", - "raw": "{\n \"fcmToken\": \"{{fcmToken}}\",\n \"platform\": \"android\",\n \"deviceId\": \"{{deviceId}}\",\n \"appVersion\": \"1.0.0\",\n \"locale\": \"ar\"\n}" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Device registered\u0027, function () { const json = pm.response.json(); pm.expect(json.message).to.eql(\u0027Device registered successfully\u0027); pm.expect(json).to.have.property(\u0027device\u0027); });" - ] - } - } - }, - { - "name": "Unregister Device FCM Token", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/devices/unregister", - "body": { - "mode": "raw", - "raw": "{\n \"fcmToken\": \"{{fcmToken}}\"\n}" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Device unregistered\u0027, function () { const json = pm.response.json(); pm.expect(json.message).to.eql(\u0027Device unregistered successfully\u0027); });" - ] - } - } - } - ] - }, - { - "name": "Collaboration Requests", - "item": [ - { - "name": "Create Collaboration Request", - "request": { - "method": "POST", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/posts/{{postId}}/collaboration-requests", - "body": { - "mode": "raw", - "raw": "{\n \"targetUserId\": \"{{targetUserId}}\"\n}" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Collaboration request response\u0027, function () { const json = pm.response.json(); pm.expect(json).to.have.property(\u0027request\u0027); });" - ] - } - } - }, - { - "name": "List My Collaboration Requests", - "request": { - "method": "GET", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/collaboration-requests?page=1\u0026limit=20", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Status is success\u0027, function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" - ] - } - } - }, - { - "name": "Approve Collaboration Request", - "request": { - "method": "PATCH", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/approve", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Approved\u0027, function () { const json = pm.response.json(); pm.expect(json.approved).to.eql(true); });" - ] - } - } - }, - { - "name": "Reject Collaboration Request", - "request": { - "method": "PATCH", - "header": { - "key": "Content-Type", - "value": "application/json" - }, - "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/reject", - "body": { - "mode": "raw", - "raw": "" - } - }, - "event": { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "pm.test(\u0027Rejected\u0027, function () { const json = pm.response.json(); pm.expect(json.rejected).to.eql(true); });" - ] - } - } - } - ] - } - ], - "variable": [ - { - "key": "baseUrl", - "value": "http://localhost:4000/api/v1", - "type": "string" - }, - { - "key": "accessToken", - "value": "", - "type": "string" - }, - { - "key": "refreshToken", - "value": "", - "type": "string" - }, - { - "key": "superAdminAccessToken", - "value": "", - "type": "string" - }, - { - "key": "superAdminRefreshToken", - "value": "", - "type": "string" - }, - { - "key": "targetAccessToken", - "value": "", - "type": "string" - }, - { - "key": "targetRefreshToken", - "value": "", - "type": "string" - }, - { - "key": "userId", - "value": "", - "type": "string" - }, - { - "key": "currentUserId", - "value": "", - "type": "string" - }, - { - "key": "targetUserId", - "value": "", - "type": "string" - }, - { - "key": "targetUsername", - "value": "", - "type": "string" - }, - { - "key": "lastFollowingState", - "value": "", - "type": "string" - }, - { - "key": "notificationUnreadCount", - "value": "", - "type": "string" - }, - { - "key": "notificationId", - "value": "", - "type": "string" - }, - { - "key": "postId", - "value": "", - "type": "string" - }, - { - "key": "ownPostId", - "value": "", - "type": "string" - }, - { - "key": "targetPostId", - "value": "", - "type": "string" - }, - { - "key": "adminPostId", - "value": "", - "type": "string" - }, - { - "key": "commentId", - "value": "", - "type": "string" - }, - { - "key": "ownCommentId", - "value": "", - "type": "string" - }, - { - "key": "replyCommentId", - "value": "", - "type": "string" - }, - { - "key": "conversationId", - "value": "", - "type": "string" - }, - { - "key": "messageId", - "value": "", - "type": "string" - }, - { - "key": "sessionJti", - "value": "", - "type": "string" - }, - { - "key": "registerEmail", - "value": "", - "type": "string" - }, - { - "key": "registerUsername", - "value": "", - "type": "string" - }, - { - "key": "adminUserId", - "value": "", - "type": "string" - }, - { - "key": "targetLoginEmail", - "value": "", - "type": "string" - }, - { - "key": "targetLoginPassword", - "value": "", - "type": "string" - }, - { - "key": "resetCode", - "value": "", - "type": "string" - }, - { - "key": "resetToken", - "value": "", - "type": "string" - }, - { - "key": "emailVerificationCode", - "value": "", - "type": "string" - }, - { - "key": "googleIdToken", - "value": "", - "type": "string" - }, - { - "key": "newPassword", - "value": "NewStrongPass123!", - "type": "string" - }, - { - "key": "feedCursor", - "value": "", - "type": "string" - }, - { - "key": "chatConversationsCursor", - "value": "", - "type": "string" - }, - { - "key": "chatMessagesCursor", - "value": "", - "type": "string" - }, - { - "key": "adminAccessToken", - "value": "", - "type": "string" - }, - { - "key": "adminRefreshToken", - "value": "", - "type": "string" - }, - { - "key": "adminEmail", - "value": "store_admin@example.com", - "type": "string" - }, - { - "key": "adminUsername", - "value": "store_admin_01", - "type": "string" - }, - { - "key": "adminPassword", - "value": "AdminStrongPass123!", - "type": "string" - }, - { - "key": "usersQuery", - "value": "user", - "type": "string" - }, - { - "key": "usersVerified", - "value": "true", - "type": "string" - }, - { - "key": "usersSortBy", - "value": "createdAt", - "type": "string" - }, - { - "key": "listSortOrder", - "value": "desc", - "type": "string" - }, - { - "key": "commentSortBy", - "value": "createdAt", - "type": "string" - }, - { - "key": "postVisibility", - "value": "public", - "type": "string" - }, - { - "key": "postSortBy", - "value": "createdAt", - "type": "string" - }, - { - "key": "postTypeFilter", - "value": "image", - "type": "string" - }, - { - "key": "postSearchQuery", - "value": "music", - "type": "string" - }, - { - "key": "postHashtag", - "value": "music", - "type": "string" - }, - { - "key": "reelQuery", - "value": "reel", - "type": "string" - }, - { - "key": "notificationRead", - "value": "false", - "type": "string" - }, - { - "key": "notificationType", - "value": "mention", - "type": "string" - }, - { - "key": "notificationResourceType", - "value": "post", - "type": "string" - }, - { - "key": "feedPreferredPostType", - "value": "video", - "type": "string" - }, - { - "key": "feedIncludeSuggestions", - "value": "true", - "type": "string" - }, - { - "key": "feedSuggestionInterval", - "value": "4", - "type": "string" - }, - { - "key": "feedFollowingOnly", - "value": "false", - "type": "string" - }, - { - "key": "feedRadiusKm", - "value": "30", - "type": "string" - }, - { - "key": "homeFeedHasMixedItems", - "value": "", - "type": "string" - }, - { - "key": "lastViewCount", - "value": "", - "type": "string" - }, - { - "key": "lastPlayCount", - "value": "", - "type": "string" - }, - { - "key": "lastShareCount", - "value": "", - "type": "string" - }, - { - "key": "marketplaceQuery", - "value": "oud", - "type": "string" - }, - { - "key": "marketplaceMinPrice", - "value": "1000", - "type": "string" - }, - { - "key": "marketplaceMaxPrice", - "value": "5000", - "type": "string" - }, - { - "key": "marketplaceIsActive", - "value": "true", - "type": "string" - }, - { - "key": "marketplaceSortBy", - "value": "price", - "type": "string" - }, - { - "key": "repairShopQuery", - "value": "riyadh", - "type": "string" - }, - { - "key": "repairShopIsActive", - "value": "true", - "type": "string" - }, - { - "key": "repairShopSortBy", - "value": "name", - "type": "string" - }, - { - "key": "instrumentId", - "value": "", - "type": "string" - }, - { - "key": "repairShopId", - "value": "", - "type": "string" - }, - { - "key": "generatedMusicUrl", - "value": "", - "type": "string" - }, - { - "key": "aiMusicPrompt", - "value": "Calm oud melody with light percussion and emotional Arabic mood", - "type": "string" - }, - { - "key": "aiMusicDuration", - "value": "12", - "type": "string" - }, - { - "key": "aiMusicSeed", - "value": "42", - "type": "string" - }, - { - "key": "listingId", - "value": "", - "type": "string" - }, - { - "key": "marketplaceListingCategory", - "value": "accessory", - "type": "string" - }, - { - "key": "marketplaceCondition", - "value": "used", - "type": "string" - }, - { - "key": "marketplaceInstrumentType", - "value": "oud", - "type": "string" - }, - { - "key": "talentRole", - "value": "instrumentalist", - "type": "string" - }, - { - "key": "talentExperienceLevel", - "value": "", - "type": "string" - }, - { - "key": "usersHasAvatar", - "value": "true", - "type": "string" - }, - { - "key": "reactionType", - "value": "love", - "type": "string" - }, - { - "key": "followRequestId", - "value": "", - "type": "string" - }, - { - "key": "reportId", - "value": "", - "type": "string" - }, - { - "key": "repostId", - "value": "", - "type": "string" - }, - { - "key": "quotePostId", - "value": "", - "type": "string" - }, - { - "key": "replyToMessageId", - "value": "" - }, - { - "key": "mediaFilePath", - "value": "" - }, - { - "key": "collaboratorId", - "value": "" - }, - { - "key": "commentKeyword", - "value": "spam" - }, - { - "key": "reportReason", - "value": "spam" - }, - { - "key": "sessionId", - "value": "" - }, - { - "key": "caseId", - "value": "" - }, - { - "key": "historyId", - "value": "" - }, - { - "key": "bulkActionTargetId", - "value": "" - }, - { - "key": "auditAction", - "value": "" - } - ] + "info": { + "name": "Oudelaa Mobile API", + "description": "Mobile/API app collection generated from Oudelaa master collection.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Auth", + "item": [ + { + "name": "Register Basic", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/register-basic", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\",\n \"confirmPassword\": \"StrongPass123!\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const ts = Date.now();", + "if (!pm.environment.get('registerEmail')) { pm.environment.set('registerEmail', `test_${ts}@example.com`); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.message).to.exist;", + "pm.expect(json.email).to.exist;", + "if (json.debugCode) { pm.environment.set('emailVerificationCode', json.debugCode); }" + ] + } + } + ] + }, + { + "name": "Register", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/register", + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Test User\",\n \"stageName\": \"Artist One\",\n \"username\": \"{{registerUsername}}\",\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\",\n \"confirmPassword\": \"StrongPass123!\",\n \"musicRoles\": [\"singer\", \"composer\"],\n \"musicGenres\": [\"Tarab\", \"Pop\"],\n \"favoriteInstruments\": [\"Oud\", \"Piano\"],\n \"favoriteMaqamat\": [\"Bayati\", \"Rast\"],\n \"location\": \"Riyadh, Saudi Arabia\",\n \"latitude\": 24.7136,\n \"longitude\": 46.6753,\n \"isPrivate\": false\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const ts = Date.now();", + "if (!pm.environment.get('registerEmail')) { pm.environment.set('registerEmail', `test_${ts}@example.com`); }", + "if (!pm.environment.get('registerUsername')) { pm.environment.set('registerUsername', `test_user_${ts}`); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.message).to.exist;", + "pm.expect(json.email).to.exist;", + "if (json.debugCode) { pm.environment.set('emailVerificationCode', json.debugCode); }" + ] + } + } + ] + }, + { + "name": "Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.user).to.exist;", + "const uid = json.user._id || json.user.id;", + "pm.environment.set('accessToken', json.accessToken);", + "pm.environment.set('refreshToken', json.refreshToken);", + "pm.environment.set('userId', uid);", + "pm.environment.set('currentUserId', uid);" + ] + } + } + ] + }, + { + "name": "Target User Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{targetLoginEmail}}\",\n \"password\": \"{{targetLoginPassword}}\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.user).to.exist;", + "const uid = json.user._id || json.user.id;", + "pm.environment.set('targetAccessToken', json.accessToken);", + "pm.environment.set('targetRefreshToken', json.refreshToken);", + "pm.environment.set('targetUserId', uid);" + ] + } + } + ] + }, + { + "name": "Admin Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{adminEmail}}\",\n \"password\": \"{{adminPassword}}\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.environment.set('adminAccessToken', json.accessToken);", + "pm.environment.set('adminRefreshToken', json.refreshToken);", + "pm.environment.set('adminUserId', (json.user && (json.user._id || json.user.id)) || pm.environment.get('adminUserId'));" + ] + } + } + ] + }, + { + "name": "Refresh", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/refresh", + "body": { + "mode": "raw", + "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" + } + }, + "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.user).to.exist;", + "pm.environment.set('accessToken', json.accessToken);", + "pm.environment.set('refreshToken', json.refreshToken);", + "pm.environment.set('userId', json.user._id || json.user.id);" + ] + } + } + ] + }, + { + "name": "Logout", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/logout", + "body": { + "mode": "raw", + "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" + } + }, + "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.message).to.eql('Logged out successfully');" + ] + } + } + ] + }, + { + "name": "Google OAuth Start (Browser)", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/auth/google", + "description": "Open this request in browser flow to start Google OAuth redirect." + } + }, + { + "name": "Google OAuth Callback (Browser Redirect)", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/auth/google/callback", + "description": "Callback endpoint is called by Google redirect after browser login. Do not call directly from Postman." + } + }, + { + "name": "Google Token Login (Mobile - Recommended)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/google/token", + "description": "Mobile-style Google login (Instagram-like): send Google idToken from app SDK.", + "body": { + "mode": "raw", + "raw": "{\n \"idToken\": \"{{googleIdToken}}\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const idToken = (pm.environment.get('googleIdToken') || '').trim();", + "if (!idToken) {", + " throw new Error('googleIdToken is empty. Paste Google idToken from Flutter/Google SDK into environment variable googleIdToken.');", + "}" + ] + } + }, + { + "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.accessToken).to.exist;", + "pm.expect(json.refreshToken).to.exist;", + "pm.expect(json.user).to.exist;", + "const uid = json.user._id || json.user.id;", + "pm.environment.set('accessToken', json.accessToken);", + "pm.environment.set('refreshToken', json.refreshToken);", + "pm.environment.set('userId', uid);", + "pm.environment.set('currentUserId', uid);" + ] + } + } + ] + }, + { + "name": "List My Sessions", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/auth/sessions" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('sessionJti', json.items[0].jti); }" + ] + } + } + ] + }, + { + "name": "Revoke My Session", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/auth/sessions/{{sessionJti}}/revoke" + }, + "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.success).to.eql(true);" + ] + } + } + ] + }, + { + "name": "Forgot Password", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/forgot-password", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\"\n}" + } + }, + "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.message).to.exist;", + "if (json.debugCode) { pm.environment.set('resetCode', json.debugCode); }" + ] + } + } + ] + }, + { + "name": "Verify Reset Code", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/verify-reset-code", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"code\": \"{{resetCode}}\"\n}" + } + }, + "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.resetToken).to.exist;", + "pm.environment.set('resetToken', json.resetToken);" + ] + } + } + ] + }, + { + "name": "Reset Password", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/reset-password", + "body": { + "mode": "raw", + "raw": "{\n \"resetToken\": \"{{resetToken}}\",\n \"newPassword\": \"{{newPassword}}\",\n \"confirmPassword\": \"{{newPassword}}\"\n}" + } + }, + "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.message).to.eql('Password reset successfully');" + ] + } + } + ] + }, + { + "name": "Send Email Verification", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/send-email-verification", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\"\n}" + } + }, + "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.message).to.exist;", + "if (json.debugCode) { pm.environment.set('emailVerificationCode', json.debugCode); }" + ] + } + } + ] + }, + { + "name": "Verify Email", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/verify-email", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"code\": \"{{emailVerificationCode}}\"\n}" + } + }, + "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.message).to.exist;", + "if (json.accessToken) { pm.environment.set('accessToken', json.accessToken); }", + "if (json.refreshToken) { pm.environment.set('refreshToken', json.refreshToken); }", + "if (json.user) { const uid = json.user._id || json.user.id; pm.environment.set('userId', uid); pm.environment.set('currentUserId', uid); }" + ] + } + } + ] + } + ] + }, + { + "name": "Media", + "item": [ + { + "name": "Media Health", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/media/health" + }, + "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.storage).to.be.an('object');", + "pm.expect(json.storage.provider).to.be.oneOf(['local', 's3']);", + "pm.expect(json.serving.rangeRequests).to.eql(true);", + "pm.expect(json.processing).to.be.an('object');" + ] + } + } + ] + } + ] + }, + { + "name": "Search", + "item": [ + { + "name": "Global Search - All", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search?q={{searchQuery}}&type={{searchType}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('query');", + "pm.expect(json).to.have.property('users');", + "pm.expect(json).to.have.property('posts');", + "pm.expect(json).to.have.property('hashtags');", + "pm.expect(json.users.pagination).to.exist;", + "pm.expect(json.posts.pagination).to.exist;", + "pm.expect(json.hashtags.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Global Search - Users Type", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search?q={{searchQuery}}&type=users&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.users).to.have.property('items');", + "pm.expect(json.users).to.have.property('pagination');" + ] + } + } + ] + }, + { + "name": "Search Users", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/users?q={{searchQuery}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " const target = json.items[0];", + " pm.expect(target).to.have.property('username');", + " pm.expect(target).to.have.property('isFollowing');", + " pm.environment.set('targetUserId', target._id || target.id);", + "}" + ] + } + } + ] + }, + { + "name": "Search Posts", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/posts?q={{searchQuery}}&page=1&limit={{searchLimit}}&sortOrder={{listSortOrder}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " const post = json.items[0];", + " pm.expect(post).to.have.property('media');", + " pm.expect(post).to.have.property('isLiked');", + " pm.expect(post).to.have.property('isSaved');", + " pm.environment.set('postId', post._id || post.id);", + "}" + ] + } + } + ] + }, + { + "name": "Search Hashtags", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/hashtags?q={{searchQuery}}&page=1&limit={{searchLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " pm.expect(json.items[0]).to.have.property('tag');", + " pm.expect(json.items[0]).to.have.property('postsCount');", + " pm.environment.set('postHashtag', json.items[0].tag);", + "}" + ] + } + } + ] + }, + { + "name": "Search Suggestions", + "request": { + "method": "GET", + "url": "{{baseUrl}}/search/suggestions?q={{searchQuery}}&limit={{searchSuggestionLimit}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('users');", + "pm.expect(json).to.have.property('hashtags');" + ] + } + } + ] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Search Users", + "request": { + "method": "GET", + "url": "{{baseUrl}}/users?q={{usersQuery}}&isVerified={{usersVerified}}&musicRole={{talentRole}}&experienceLevel={{talentExperienceLevel}}&hasAvatar={{usersHasAvatar}}&sortBy={{usersSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=20", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.pagination).to.exist;", + "const currentUserId = pm.environment.get('currentUserId');", + "if (json.items && json.items.length > 0) {", + " const target = json.items.find(u => (u._id || u.id) !== currentUserId) || json.items[0];", + " const targetId = target._id || target.id;", + " pm.environment.set('targetUserId', targetId);", + " if (target.username) { pm.environment.set('targetUsername', target.username); }", + "}" + ] + } + } + ] + }, + { + "name": "Profile Setup", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/users/me/profile-setup", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "stageName", + "value": "Artist One", + "type": "text" + }, + { + "key": "avatarFile", + "type": "file", + "src": [] + }, + { + "key": "bio", + "value": "Short bio", + "type": "text" + }, + { + "key": "location", + "value": "Riyadh, Saudi Arabia", + "type": "text" + }, + { + "key": "latitude", + "value": "24.7136", + "type": "text" + }, + { + "key": "coverImageFile", + "type": "file", + "src": [], + "disabled": true + }, + { + "key": "longitude", + "value": "46.6753", + "type": "text" + } + ] + } + }, + "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.location).to.eql('Riyadh, Saudi Arabia');", + "pm.expect(json.latitude).to.eql(24.7136);", + "pm.expect(json.longitude).to.eql(46.6753);", + "if (json.avatar) { pm.expect(json.avatar).to.be.a('string'); }", + "if (json.coverImage) { pm.expect(json.coverImage).to.be.a('string'); }" + ] + } + } + ] + }, + { + "name": "Music Setup", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/users/me/music-setup", + "body": { + "mode": "raw", + "raw": "{\n \"musicRoles\": [\"instrumentalist\", \"composer\"],\n \"musicGenres\": [\"Tarab\", \"Pop\"],\n \"experienceLevel\": \"intermediate\",\n \"favoriteInstruments\": [\"Oud\", \"Piano\"],\n \"favoriteMaqamat\": [\"Bayati\", \"Rast\"]\n}" + } + }, + "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.musicRoles).to.include('instrumentalist');", + "pm.expect(json.musicRoles).to.include('composer');", + "pm.expect(json.experienceLevel).to.eql('intermediate');", + "pm.expect(json).to.not.have.property('isInstrumentalist');", + "pm.expect(json).to.not.have.property('isSinger');", + "pm.expect(json).to.not.have.property('isComposer');", + "pm.expect(json).to.not.have.property('isLyricist');" + ] + } + } + ] + }, + { + "name": "Update Me", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/users/me", + "body": { + "mode": "raw", + "raw": "{\n \"bio\": \"Updated from Postman\",\n \"avatar\": \"https://cdn.example.com/avatar.jpg\",\n \"stageName\": \"Artist One Updated\",\n \"musicGenres\": [\"Tarab\", \"Khaleeji\"],\n \"favoriteInstruments\": [\"Oud\"],\n \"favoriteMaqamat\": [\"Hijaz\"],\n \"location\": \"Jeddah, Saudi Arabia\"\n}" + } + }, + "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.bio).to.eql('Updated from Postman');", + "if (json.coverImage) { pm.expect(json.coverImage).to.be.a('string'); }" + ] + } + } + ] + }, + { + "name": "Update Me With Files", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/users/me", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "stageName", + "value": "Artist One Files", + "type": "text" + }, + { + "key": "bio", + "value": "Updated from Postman with files", + "type": "text" + }, + { + "key": "location", + "value": "Riyadh, Saudi Arabia", + "type": "text" + }, + { + "key": "avatar", + "value": "https://cdn.example.com/avatar.jpg", + "type": "text" + }, + { + "key": "avatarFile", + "type": "file", + "src": [] + }, + { + "key": "coverImageFile", + "type": "file", + "src": [], + "disabled": true + } + ] + } + }, + "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.stageName).to.eql('Artist One Files');", + "if (json.avatar) { pm.expect(json.avatar).to.be.a('string'); }", + "if (json.coverImage) { pm.expect(json.coverImage).to.be.a('string'); }" + ] + } + } + ] + }, + { + "name": "Get User By Id", + "request": { + "method": "GET", + "url": "{{baseUrl}}/users/{{userId}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + } + }, + { + "name": "Discover Talents", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/users/discover?q={{usersQuery}}&musicRole={{talentRole}}&experienceLevel={{talentExperienceLevel}}&hasAvatarOnly={{usersHasAvatar}}&includeRoleBuckets=true&sortBy=followersCount&sortOrder={{listSortOrder}}&page=1&limit=8" + }, + "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.items).to.be.an('array');", + "pm.expect(json).to.have.property('roleBuckets');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('targetUserId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Get Profile Overview", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/users/{{targetUserId}}/profile-overview" + }, + "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('stats');", + "pm.expect(json).to.have.property('contentCounts');", + "pm.expect(json).to.have.property('viewerState');" + ] + } + } + ] + }, + { + "name": "Get User Presence", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/presence", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Presence shape', function () { const json = pm.response.json(); pm.expect(json).to.have.property('userId'); pm.expect(json).to.have.property('isOnline'); pm.expect(json).to.have.property('lastSeenAt'); });" + ] + } + } + }, + { + "name": "Get User Posts By User Id", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/posts?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Paginated user posts', function () { const json = pm.response.json(); pm.expect(json).to.have.property('items'); pm.expect(json).to.have.property('pagination'); });" + ] + } + } + } + ] + }, + { + "name": "Posts", + "item": [ + { + "name": "Create Post (Own)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "First text post with mention @{{targetUsername}} #music", + "type": "text" + }, + { + "key": "taggedUserIds", + "value": "{{targetUserId}}", + "type": "text" + }, + { + "key": "mentionUsernames", + "value": "{{targetUsername}}", + "type": "text" + }, + { + "key": "location", + "value": "Riyadh, Saudi Arabia", + "type": "text" + }, + { + "key": "latitude", + "value": "24.7136", + "type": "text" + }, + { + "key": "longitude", + "value": "46.6753", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('accessToken')) { throw new Error('Missing environment var: accessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "const pid = json._id || json.id;", + "pm.environment.set('postId', pid);", + "pm.environment.set('ownPostId', pid);" + ] + } + } + ] + }, + { + "name": "Create Carousel Post (Instagram Style)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Weekend jam session #oud #music", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + }, + { + "key": "taggedUserIds", + "value": "{{targetUserId}}", + "type": "text" + }, + { + "key": "mentionUsernames", + "value": "{{targetUsername}}", + "type": "text" + }, + { + "key": "location", + "value": "Riyadh, Saudi Arabia", + "type": "text" + }, + { + "key": "latitude", + "value": "24.7136", + "type": "text" + }, + { + "key": "longitude", + "value": "46.6753", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [] + }, + { + "key": "imageFiles", + "type": "file", + "src": [] + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('accessToken')) { throw new Error('Missing environment var: accessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.postType).to.eql('image');", + "pm.expect(json.processingStatus).to.eql('ready');", + "pm.expect(json.media).to.be.an('object');", + "pm.expect(json.media.mediaType).to.eql(json.postType);", + "pm.expect(json.imageUrls).to.be.an('array');", + "pm.expect(json.media.images).to.be.an('array').that.is.not.empty;", + "pm.expect(json.media.images[0].url).to.be.a('string');", + "pm.expect(json.imageUrls.length).to.be.greaterThan(0);", + "const pid = json._id || json.id;", + "pm.environment.set('postId', pid);", + "pm.environment.set('ownPostId', pid);" + ] + } + } + ] + }, + { + "name": "Update Carousel Post (Instagram Style)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/{{postId}}", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Updated carousel caption #oud #live", + "type": "text" + }, + { + "key": "imageFiles", + "type": "file", + "src": [] + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.postType).to.eql('image');", + "pm.expect(json.imageUrls).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Get User Posts (Own)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/posts/user/{{userId}}?visibility={{postVisibility}}&sortBy={{postSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=10", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('userId')) missing.push('userId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (!pm.environment.get('postId') && json.items && json.items.length > 0) {", + " pm.environment.set('postId', json.items[0]._id || json.items[0].id);", + " pm.environment.set('ownPostId', json.items[0]._id || json.items[0].id);", + "}" + ] + } + } + ] + }, + { + "name": "Get User Posts Filtered", + "request": { + "method": "GET", + "url": "{{baseUrl}}/posts/user/{{userId}}?visibility={{postVisibility}}&postType={{postTypeFilter}}&q={{postSearchQuery}}&hashtag={{postHashtag}}&sortBy={{postSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=10", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Get Post By Id", + "request": { + "method": "GET", + "url": "{{baseUrl}}/posts/{{postId}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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._id || json.id).to.exist;", + "pm.expect(json).to.have.property('content');" + ] + } + } + ] + }, + { + "name": "Register Post View", + "request": { + "method": "POST", + "url": "{{baseUrl}}/posts/{{postId}}/view", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.success).to.eql(true);", + "pm.expect(json).to.have.property('viewCount');", + "pm.environment.set('lastViewCount', json.viewCount);" + ] + } + } + ] + }, + { + "name": "Register Post Share", + "request": { + "method": "POST", + "url": "{{baseUrl}}/posts/{{postId}}/share", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.success).to.eql(true);", + "pm.expect(json).to.have.property('shareCount');", + "pm.environment.set('lastShareCount', json.shareCount);" + ] + } + } + ] + }, + { + "name": "Update Post", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/{{postId}}", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Updated post content with @{{targetUsername}} #live", + "type": "text" + }, + { + "key": "taggedUserIds", + "value": "{{targetUserId}}", + "type": "text" + }, + { + "key": "mentionUsernames", + "value": "{{targetUsername}}", + "type": "text" + }, + { + "key": "location", + "value": "Jeddah, Saudi Arabia", + "type": "text" + }, + { + "key": "latitude", + "value": "21.5433", + "type": "text" + }, + { + "key": "longitude", + "value": "39.1728", + "type": "text" + } + ] + } + }, + "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.content).to.eql('Updated post content with @artist_one #live');" + ] + } + } + ] + }, + { + "name": "Create Video Post (Own)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Video post #music", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + }, + { + "key": "durationSeconds", + "value": "42", + "type": "text" + }, + { + "key": "coverImageFile", + "type": "file", + "src": [] + }, + { + "key": "style", + "value": "Sharqi", + "type": "text" + }, + { + "key": "maqam", + "value": "Hijaz", + "type": "text" + }, + { + "key": "mentionUsernames", + "value": "{{targetUsername}}", + "type": "text" + }, + { + "key": "rhythmSignature", + "value": "6/8", + "type": "text" + }, + { + "key": "location", + "value": "Riyadh, Saudi Arabia", + "type": "text" + }, + { + "key": "latitude", + "value": "24.7136", + "type": "text" + }, + { + "key": "longitude", + "value": "46.6753", + "type": "text" + }, + { + "key": "videoFile", + "type": "file", + "src": [] + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('accessToken')) { throw new Error('Missing environment var: accessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.postType).to.eql('video');", + "pm.expect(json.media.thumbnailUrl).to.be.a('string');", + "pm.expect(json.media.preferredPlaybackUrl).to.be.a('string');", + "if (json.hlsUrl) { pm.expect(json.media.preferredPlaybackUrl).to.eql(json.hlsUrl); }", + "pm.expect(json.processingStatus).to.eql('ready');", + "pm.expect(json.media).to.be.an('object');", + "pm.expect(json.media.mediaType).to.eql(json.postType);", + "pm.expect(json.durationSeconds).to.eql(42);", + "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a('string'); }", + "pm.expect(json.style).to.eql('Sharqi');", + "pm.expect(json.maqam).to.eql('Hijaz');", + "pm.expect(json.rhythmSignature).to.eql('6/8');", + "pm.environment.set('postId', json._id || json.id);", + "pm.environment.set('ownPostId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "Create Reel (Video First)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/reels", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "New reel from oud session #reel", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + }, + { + "key": "durationSeconds", + "value": "30", + "type": "text" + }, + { + "key": "coverImageFile", + "type": "file", + "src": [] + }, + { + "key": "style", + "value": "Sharqi", + "type": "text" + }, + { + "key": "maqam", + "value": "Hijaz", + "type": "text" + }, + { + "key": "rhythmSignature", + "value": "6/8", + "type": "text" + }, + { + "key": "videoFile", + "type": "file", + "src": [] + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('accessToken')) { throw new Error('Missing environment var: accessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.postType).to.eql('video');", + "pm.expect(json.media.thumbnailUrl).to.be.a('string');", + "pm.expect(json.media.preferredPlaybackUrl).to.be.a('string');", + "if (json.hlsUrl) { pm.expect(json.media.preferredPlaybackUrl).to.eql(json.hlsUrl); }", + "pm.expect(json.processingStatus).to.eql('ready');", + "pm.expect(json.media).to.be.an('object');", + "pm.expect(json.media.mediaType).to.eql(json.postType);", + "pm.expect(json.durationSeconds).to.eql(30);", + "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a('string'); }", + "pm.expect(json.style).to.eql('Sharqi');", + "pm.expect(json.maqam).to.eql('Hijaz');", + "pm.expect(json.rhythmSignature).to.eql('6/8');", + "pm.environment.set('postId', json._id || json.id);", + "pm.environment.set('ownPostId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "Get Reels", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/reels?visibility={{postVisibility}}&authorId={{userId}}&sortBy={{postSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=10" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Get Reels Filtered", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/reels?visibility={{postVisibility}}&authorId={{userId}}&q={{reelQuery}}&sortBy={{postSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=10" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Create Audio Post (Own)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Audio post #oud", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + }, + { + "key": "durationSeconds", + "value": "54", + "type": "text" + }, + { + "key": "coverImageFile", + "type": "file", + "src": [] + }, + { + "key": "style", + "value": "Sharqi", + "type": "text" + }, + { + "key": "maqam", + "value": "Hijaz", + "type": "text" + }, + { + "key": "rhythmSignature", + "value": "6/8", + "type": "text" + }, + { + "key": "audioFile", + "type": "file", + "src": [] + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('accessToken')) { throw new Error('Missing environment var: accessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.postType).to.eql('audio');", + "pm.expect(json.media.audioUrl).to.be.a('string');", + "pm.expect(json.media.waveformPeaks).to.be.an('array');", + "pm.expect(json.media.durationSeconds).to.exist;", + "pm.expect(json.processingStatus).to.eql('ready');", + "pm.expect(json.media).to.be.an('object');", + "pm.expect(json.media.mediaType).to.eql(json.postType);", + "pm.expect(json.durationSeconds).to.eql(54);", + "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a('string'); }", + "pm.expect(json.style).to.eql('Sharqi');", + "pm.expect(json.maqam).to.eql('Hijaz');", + "pm.expect(json.rhythmSignature).to.eql('6/8');", + "pm.environment.set('postId', json._id || json.id);", + "pm.environment.set('ownPostId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "Create Audio Post (URL + Waveform)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Audio post with waveform #oud #hijaz", + "type": "text" + }, + { + "key": "audioUrl", + "value": "https://cdn.example.com/audio-sample.mp3", + "type": "text" + }, + { + "key": "durationSeconds", + "value": "54", + "type": "text" + }, + { + "key": "thumbnailUrl", + "value": "https://cdn.example.com/audio-cover.jpg", + "type": "text" + }, + { + "key": "style", + "value": "Sharqi", + "type": "text" + }, + { + "key": "maqam", + "value": "Hijaz", + "type": "text" + }, + { + "key": "rhythmSignature", + "value": "6/8", + "type": "text" + }, + { + "key": "waveformPeaks", + "value": "[12,38,27,49,22,44]", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('accessToken')) { throw new Error('Missing environment var: accessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.postType).to.eql('audio');", + "pm.expect(json.media.audioUrl).to.be.a('string');", + "pm.expect(json.media.waveformPeaks).to.be.an('array');", + "pm.expect(json.media.durationSeconds).to.exist;", + "pm.expect(json.processingStatus).to.eql('ready');", + "pm.expect(json.media).to.be.an('object');", + "pm.expect(json.media.mediaType).to.eql(json.postType);", + "pm.expect(json.waveformPeaks).to.be.an('array');", + "pm.expect(json.waveformPeaks.length).to.eql(6);", + "pm.expect(json.maqam).to.eql('Hijaz');", + "pm.environment.set('postId', json._id || json.id);", + "pm.environment.set('ownPostId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "Register Post Play (Audio/Video)", + "request": { + "method": "POST", + "url": "{{baseUrl}}/posts/{{postId}}/play", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.success).to.eql(true);", + "pm.expect(json).to.have.property('playCount');", + "pm.environment.set('lastPlayCount', json.playCount);" + ] + } + } + ] + }, + { + "name": "Create Invalid Post (Video+Audio)", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Invalid post", + "type": "text" + }, + { + "key": "videoUrl", + "value": "https://cdn.example.com/video.mp4", + "type": "text" + }, + { + "key": "audioUrl", + "value": "https://cdn.example.com/audio.mp3", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('accessToken')) { throw new Error('Missing environment var: accessToken'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 400', function () { pm.response.to.have.status(400); });" + ] + } + } + ] + }, + { + "name": "Update Post Video (Form-Data)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/{{postId}}", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Updated with video file", + "type": "text" + }, + { + "key": "durationSeconds", + "value": "58", + "type": "text" + }, + { + "key": "coverImageFile", + "type": "file", + "src": [] + }, + { + "key": "style", + "value": "Contemporary", + "type": "text" + }, + { + "key": "maqam", + "value": "Bayati", + "type": "text" + }, + { + "key": "rhythmSignature", + "value": "4/4", + "type": "text" + }, + { + "key": "videoFile", + "type": "file", + "src": [] + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.postType).to.eql('video');", + "pm.expect(json.durationSeconds).to.eql(58);", + "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a('string'); }", + "pm.expect(json.style).to.eql('Contemporary');", + "pm.expect(json.maqam).to.eql('Bayati');", + "pm.expect(json.rhythmSignature).to.eql('4/4');" + ] + } + } + ] + }, + { + "name": "Update Post Audio (Form-Data)", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/{{postId}}", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Updated with audio file", + "type": "text" + }, + { + "key": "durationSeconds", + "value": "61", + "type": "text" + }, + { + "key": "coverImageFile", + "type": "file", + "src": [] + }, + { + "key": "style", + "value": "Tarab", + "type": "text" + }, + { + "key": "maqam", + "value": "Nahawand", + "type": "text" + }, + { + "key": "rhythmSignature", + "value": "6/8", + "type": "text" + }, + { + "key": "audioFile", + "type": "file", + "src": [] + } + ] + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "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.postType).to.eql('audio');", + "pm.expect(json.durationSeconds).to.eql(61);", + "if (json.thumbnailUrl) { pm.expect(json.thumbnailUrl).to.be.a('string'); }", + "pm.expect(json.style).to.eql('Tarab');", + "pm.expect(json.maqam).to.eql('Nahawand');", + "pm.expect(json.rhythmSignature).to.eql('6/8');" + ] + } + } + ] + }, + { + "name": "Delete Post (Cleanup - Run Last)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/{{postId}}" + }, + "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.success).to.eql(true);" + ] + } + } + ] + }, + { + "name": "Repost Post", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/posts/{{postId}}/repost", + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"\"\n}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.environment.set('repostId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "Quote Post", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/posts/{{postId}}/repost", + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"My take on this post\"\n}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.environment.set('quotePostId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "Update Post Comment Settings", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/{{postId}}/comment-settings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "{{postId}}", + "comment-settings" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"commentsDisabled\": false,\n \"commentsFollowersOnly\": true,\n \"commentFilterKeywords\": [\n \"{{commentKeyword}}\",\n \"offensive\"\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Pin Post To Profile", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/{{postId}}/pin-profile", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "{{postId}}", + "pin-profile" + ], + "query": [] + } + } + }, + { + "name": "Unpin Post From Profile", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/{{postId}}/unpin-profile", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "{{postId}}", + "unpin-profile" + ], + "query": [] + } + } + }, + { + "name": "Archive Post", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/{{postId}}/archive", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "{{postId}}", + "archive" + ], + "query": [] + } + } + }, + { + "name": "Restore Archived Post", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/{{postId}}/restore-archive", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "{{postId}}", + "restore-archive" + ], + "query": [] + } + } + }, + { + "name": "Create Carousel Post With Metadata", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/posts", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts" + ], + "query": [] + }, + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Carousel caption #oud", + "type": "text" + }, + { + "key": "imageUrls", + "value": "https://cdn.example.com/1.jpg,https://cdn.example.com/2.jpg", + "type": "text" + }, + { + "key": "imageCaptions", + "value": "First image,Second image", + "type": "text" + }, + { + "key": "imageAltTexts", + "value": "Oud on stage,Audience closeup", + "type": "text" + }, + { + "key": "collaboratorIds", + "value": "{{collaboratorId}}", + "type": "text" + }, + { + "key": "commentsDisabled", + "value": "false", + "type": "text" + }, + { + "key": "commentsFollowersOnly", + "value": "false", + "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 value = json.item;", + "if (value) pm.environment.set(\"postId\", value._id || value.id || value);" + ] + } + } + ] + }, + { + "name": "Update Carousel Alt Text", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/{{postId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "{{postId}}" + ], + "query": [] + }, + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "imageUrls", + "value": "https://cdn.example.com/1.jpg,https://cdn.example.com/2.jpg", + "type": "text" + }, + { + "key": "imageCaptions", + "value": "Updated first,Updated second", + "type": "text" + }, + { + "key": "imageAltTexts", + "value": "Updated oud alt,Updated audience alt", + "type": "text" + } + ] + } + } + }, + { + "name": "Disable Comments On Post", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/{{postId}}/comment-settings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "{{postId}}", + "comment-settings" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"commentsDisabled\": true,\n \"commentsFollowersOnly\": false,\n \"commentFilterKeywords\": []\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Enable Comments On Post", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/posts/{{postId}}/comment-settings", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "posts", + "{{postId}}", + "comment-settings" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"commentsDisabled\": false,\n \"commentsFollowersOnly\": false,\n \"commentFilterKeywords\": []\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Collaboration Requests", + "item": [ + { + "name": "Create Collaboration Request", + "request": { + "method": "POST", + "url": "{{baseUrl}}/posts/{{postId}}/collaboration-requests", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"targetUserId\": \"{{targetUserId}}\",\n \"collaborationType\": \"{{collaborationType}}\",\n \"message\": \"{{collaborationMessage}}\",\n \"attachmentUrl\": \"{{collaborationAttachmentUrl}}\",\n \"attachmentType\": \"{{collaborationAttachmentType}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200 or 201', function () { pm.expect([200, 201]).to.include(pm.response.code); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('request');", + "pm.environment.set('collaborationRequestId', json.request._id || json.request.id);", + "pm.test('Rich collaboration fields are accepted', function () {", + " pm.expect(json.request).to.have.property('collaborationType');", + " pm.expect(json.request).to.have.property('message');", + " pm.expect(json.request).to.have.property('attachmentUrl');", + " pm.expect(json.request).to.have.property('attachmentType');", + "});" + ] + } + } + ] + }, + { + "name": "List My Collaboration Requests", + "request": { + "method": "GET", + "url": "{{baseUrl}}/collaboration-requests?page=1&limit=20", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('items');", + "pm.expect(json).to.have.property('pagination');", + "if (json.items.length) {", + " pm.environment.set('collaborationRequestId', json.items[0]._id || json.items[0].id);", + " pm.expect(json.items[0]).to.have.property('status');", + " pm.expect(json.items[0]).to.have.property('collaborationType');", + "}" + ] + } + } + ] + }, + { + "name": "Approve Collaboration Request", + "request": { + "method": "PATCH", + "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/approve", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('approved', true);", + "pm.expect(json).to.have.property('request');" + ] + } + } + ] + }, + { + "name": "Reject Collaboration Request", + "request": { + "method": "PATCH", + "url": "{{baseUrl}}/collaboration-requests/{{collaborationRequestId}}/reject", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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('rejected', true);", + "pm.expect(json).to.have.property('request');" + ] + } + } + ] + } + ] + }, + { + "name": "Comments", + "item": [ + { + "name": "Create Comment", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/comments", + "body": { + "mode": "raw", + "raw": "{\n \"postId\": \"{{postId}}\",\n \"content\": \"Awesome post!\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "const cid = json._id || json.id;", + "pm.environment.set('commentId', cid);", + "pm.environment.set('ownCommentId', cid);" + ] + } + } + ] + }, + { + "name": "Create Comment With Mention", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/comments", + "body": { + "mode": "raw", + "raw": "{\n \"postId\": \"{{postId}}\",\n \"content\": \"Awesome post @{{targetUsername}}!\",\n \"mentionUsernames\": [\"{{targetUsername}}\"]\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (!pm.environment.get('targetUsername')) missing.push('targetUsername');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "const cid = json._id || json.id;", + "pm.environment.set('commentId', cid);", + "pm.expect(json.mentionUsernames || []).to.include(pm.environment.get('targetUsername').toLowerCase());" + ] + } + } + ] + }, + { + "name": "Get Post Comments", + "request": { + "method": "GET", + "url": "{{baseUrl}}/comments/post/{{postId}}?page=1&limit=10&sortBy={{commentSortBy}}&sortOrder={{listSortOrder}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Create Comment Reply", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/comments/{{commentId}}/replies", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "comments", + "{{commentId}}", + "replies" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"Reply to comment\"\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();", + "const value = json.item;", + "if (value) pm.environment.set(\"replyCommentId\", value._id || value.id || value);" + ] + } + } + ] + }, + { + "name": "Get Comment Replies", + "request": { + "method": "GET", + "url": "{{baseUrl}}/comments/{{commentId}}/replies?page=1&limit=10&sortBy={{commentSortBy}}&sortOrder={{listSortOrder}}", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Update Comment", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/comments/{{commentId}}", + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"Edited comment @{{secondaryUsername}}\",\n \"mentionUsernames\": [\"{{secondaryUsername}}\"]\n}" + } + }, + "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.content).to.include('Edited comment');" + ] + } + } + ] + }, + { + "name": "Delete Comment", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/comments/{{commentId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Pin Comment", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/comments/{{commentId}}/pin", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "comments", + "{{commentId}}", + "pin" + ], + "query": [] + } + } + }, + { + "name": "Unpin Comment", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/comments/{{commentId}}/unpin", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "comments", + "{{commentId}}", + "unpin" + ], + "query": [] + } + } + }, + { + "name": "Create Comment Hidden By Filter", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/comments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "comments" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"postId\": \"{{postId}}\",\n \"content\": \"This contains {{commentKeyword}} keyword\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get Post Comments - Top", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/comments/post/{{postId}}?page=1&limit=20&sortBy=top&sortOrder=desc", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "comments", + "post", + "{{postId}}" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "sortBy", + "value": "top" + }, + { + "key": "sortOrder", + "value": "desc" + } + ] + } + } + }, + { + "name": "Get Post Comments - Newest", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/comments/post/{{postId}}?page=1&limit=20&sortBy=createdAt&sortOrder=desc", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "comments", + "post", + "{{postId}}" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "sortBy", + "value": "createdAt" + }, + { + "key": "sortOrder", + "value": "desc" + } + ] + } + } + } + ] + }, + { + "name": "Likes", + "item": [ + { + "name": "Like Post (Set liked=true)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes", + "body": { + "mode": "raw", + "raw": "{\n \"targetId\": \"{{postId}}\",\n \"targetType\": \"post\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.liked).to.eql(true);" + ] + } + } + ] + }, + { + "name": "Unlike Post (Set liked=false)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes/post/{{postId}}" + }, + "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.liked).to.eql(false);" + ] + } + } + ] + }, + { + "name": "Get Post Like Status", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes/status/post/{{postId}}" + }, + "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('liked');" + ] + } + } + ] + }, + { + "name": "Like Comment", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes", + "body": { + "mode": "raw", + "raw": "{\n \"targetId\": \"{{commentId}}\",\n \"targetType\": \"comment\"\n}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.liked).to.eql(true);" + ] + } + } + ] + }, + { + "name": "Unlike Comment", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes/comment/{{commentId}}" + }, + "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.liked).to.eql(false);" + ] + } + } + ] + }, + { + "name": "Get Comment Like Status", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes/status/comment/{{commentId}}" + }, + "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('liked');" + ] + } + } + ] + }, + { + "name": "React To Post", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes", + "body": { + "mode": "raw", + "raw": "{\n \"targetId\": \"{{postId}}\",\n \"targetType\": \"post\",\n \"reactionType\": \"{{reactionType}}\"\n}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.reactionType).to.exist;" + ] + } + } + ] + }, + { + "name": "Reaction Status - Post", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes/status/post/{{postId}}" + }, + "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('reactionSummary');" + ] + } + } + ] + }, + { + "name": "Toggle Like", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/likes/toggle", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "likes", + "toggle" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"targetType\": \"post\",\n \"targetId\": \"{{postId}}\",\n \"reactionType\": \"like\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Saves", + "item": [ + { + "name": "Save Post (Set saved=true)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/saves", + "body": { + "mode": "raw", + "raw": "{\n \"postId\": \"{{postId}}\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('postId')) missing.push('postId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.saved).to.eql(true);" + ] + } + } + ] + }, + { + "name": "Unsave Post (Set saved=false)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/saves/{{postId}}" + }, + "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.saved).to.eql(false);" + ] + } + } + ] + }, + { + "name": "Get Save Status", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/saves/status/{{postId}}" + }, + "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('saved');" + ] + } + } + ] + }, + { + "name": "Get My Saved Posts", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/saves/me?page=1&limit=10&sortOrder={{listSortOrder}}" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Toggle Save", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/saves/toggle", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "saves", + "toggle" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"postId\": \"{{postId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Follows", + "item": [ + { + "name": "Follow User (Instagram Style)", + "request": { + "method": "POST", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{targetUserId}}/follow", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Follow response', function () { const json = pm.response.json(); pm.expect(json).to.have.property('isFollowing'); pm.expect(json).to.have.property('targetUserId'); });" + ] + } + } + }, + { + "name": "Unfollow User (Instagram Style)", + "request": { + "method": "DELETE", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{targetUserId}}/follow", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Unfollow response', function () { const json = pm.response.json(); pm.expect(json.isFollowing).to.eql(false); });" + ] + } + } + }, + { + "name": "Get User Follow Status", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{targetUserId}}/follow-status", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Follow status shape', function () { const json = pm.response.json(); pm.expect(json).to.have.property('isFollowing'); pm.expect(json).to.have.property('isFollowedBy'); pm.expect(json).to.have.property('isMutual'); });" + ] + } + } + }, + { + "name": "Get User Followers", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/followers?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Followers list shape', function () { const json = pm.response.json(); pm.expect(json).to.have.property('items'); pm.expect(json).to.have.property('pagination'); });" + ] + } + } + }, + { + "name": "Get User Following", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/{{userId}}/following?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Following list shape', function () { const json = pm.response.json(); pm.expect(json).to.have.property('items'); pm.expect(json).to.have.property('pagination'); });" + ] + } + } + }, + { + "name": "Get My Followers", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/me/followers?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Get My Following", + "request": { + "method": "GET", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/users/me/following?page=1&limit=20&sortOrder={{listSortOrder}}", + "body": { + "mode": "raw", + "raw": "" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is success', function () { pm.expect(pm.response.code).to.be.oneOf([200, 201, 204]); });" + ] + } + } + }, + { + "name": "Toggle Follow User", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/follows/toggle", + "body": { + "mode": "raw", + "raw": "{\n \"targetUserId\": \"{{targetUserId}}\"\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('targetUserId')) missing.push('targetUserId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json).to.have.property('following');", + "pm.environment.set('lastFollowingState', json.following ? 'following' : 'not_following');" + ] + } + } + ] + }, + { + "name": "Get Followers", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/follows/followers/{{userId}}?page=1&limit=10&sortOrder={{listSortOrder}}" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Get Following", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/follows/following/{{userId}}?page=1&limit=10&sortOrder={{listSortOrder}}" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Get Follow Status", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/follows/status/{{targetUserId}}" + }, + "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('following');" + ] + } + } + ] + }, + { + "name": "Get Follow Suggestions", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/follows/suggestions?page=1&limit=10&sortOrder={{listSortOrder}}" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "List Follow Requests", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/follows/requests?page=1&limit=20" + }, + "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();", + "if (json.items && json.items.length) { pm.environment.set('followRequestId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Approve Follow Request", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/follows/requests/{{followRequestId}}/approve" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Reject Follow Request", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/follows/requests/{{followRequestId}}/reject" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + } + ] + }, + { + "name": "Notifications", + "item": [ + { + "name": "Get My Notifications", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications?read={{notificationRead}}&sortOrder={{listSortOrder}}&page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) {", + " pm.environment.set('notificationId', json.items[0]._id || json.items[0].id);", + " pm.expect(json.items[0]).to.have.property('title');", + " pm.expect(json.items[0]).to.have.property('resourceType');", + " pm.expect(json.items[0]).to.have.property('deepLink');", + "}" + ] + } + } + ] + }, + { + "name": "Get My Notifications Filtered", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications?read={{notificationRead}}&type={{notificationType}}&resourceType={{notificationResourceType}}&sortOrder={{listSortOrder}}&page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;" + ] + } + } + ] + }, + { + "name": "Get Unread Notifications Count", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/unread-count" + }, + "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');" + ] + } + } + ] + }, + { + "name": "Mark Notification Read", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/{{notificationId}}/read" + }, + "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');" + ] + } + } + ] + }, + { + "name": "Mark All Notifications Read", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/notifications/read-all" + }, + "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.unreadCount).to.eql(0);" + ] + } + } + ] + } + ] + }, + { + "name": "Chat", + "item": [ + { + "name": "Create Conversation", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/conversations", + "body": { + "mode": "raw", + "raw": "{\n \"participantIds\": [\"{{targetUserId}}\"],\n \"isGroup\": false\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "const missing = [];", + "if (!pm.environment.get('accessToken')) missing.push('accessToken');", + "if (!pm.environment.get('targetUserId')) missing.push('targetUserId');", + "if (missing.length) { throw new Error('Missing environment vars: ' + missing.join(', ')); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.environment.set('conversationId', json._id || json.id);" + ] + } + } + ] + }, + { + "name": "Get My Conversations", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/conversations?limit=20&cursor={{chatConversationsCursor}}&sortOrder={{listSortOrder}}" + }, + "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.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('conversationId', json.items[0]._id || json.items[0].id); }", + "pm.environment.set('chatConversationsCursor', json.nextCursor || '');" + ] + } + } + ] + }, + { + "name": "Send Message", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/chat/messages", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "chat", + "messages" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"conversationId\": \"{{conversationId}}\",\n \"content\": \"Hello from Postman\"\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();", + "const value = json.item;", + "if (value) pm.environment.set(\"messageId\", value._id || value.id || value);" + ] + } + } + ] + }, + { + "name": "Get Conversation Messages", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/conversations/{{conversationId}}/messages?limit=20&cursor={{chatMessagesCursor}}&sortOrder={{listSortOrder}}" + }, + "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.pagination).to.exist;", + "pm.environment.set('chatMessagesCursor', json.nextCursor || '');" + ] + } + } + ] + }, + { + "name": "Mark Message Seen", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/messages/{{messageId}}/seen" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Unsend Message", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/messages/{{messageId}}/unsend" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Block User In Chat", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/blocks/{{targetUserId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.blocked).to.eql(true);" + ] + } + } + ] + }, + { + "name": "Unblock User In Chat", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/blocks/{{targetUserId}}/unblock" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Get Chat Block Status", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/blocks/status/{{targetUserId}}" + }, + "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('iBlocked');", + "pm.expect(json).to.have.property('blockedMe');" + ] + } + } + ] + }, + { + "name": "Get My Chat Blocks", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/chat/blocks" + }, + "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.items).to.be.an('array');" + ] + } + } + ] + }, + { + "name": "Send Reply Message", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/chat/messages", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "chat", + "messages" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"conversationId\": \"{{conversationId}}\",\n \"content\": \"Replying to a message\",\n \"replyToMessageId\": \"{{replyToMessageId}}\"\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();", + "const value = json.item;", + "if (value) pm.environment.set(\"messageId\", value._id || value.id || value);" + ] + } + } + ] + }, + { + "name": "Send Message Media Upload", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": { + "raw": "{{baseUrl}}/chat/messages/upload", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "chat", + "messages", + "upload" + ], + "query": [] + }, + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "conversationId", + "value": "{{conversationId}}", + "type": "text" + }, + { + "key": "content", + "value": "Media message caption", + "type": "text" + }, + { + "key": "replyToMessageId", + "value": "{{replyToMessageId}}", + "type": "text" + }, + { + "key": "mediaFile", + "type": "file", + "src": "{{mediaFilePath}}" + } + ] + } + }, + "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 value = json.item;", + "if (value) pm.environment.set(\"messageId\", value._id || value.id || value);" + ] + } + } + ] + }, + { + "name": "React To Message", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/chat/messages/{{messageId}}/reaction", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "chat", + "messages", + "{{messageId}}", + "reaction" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"reactionType\": \"love\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete Message For Me", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/chat/messages/{{messageId}}/delete-for-me", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "chat", + "messages", + "{{messageId}}", + "delete-for-me" + ], + "query": [] + } + } + } + ] + }, + { + "name": "Feed", + "item": [ + { + "name": "Get My Feed Mixed Home", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/feed/me?page=1&limit=20&includeSuggestions={{feedIncludeSuggestions}}&suggestionInterval={{feedSuggestionInterval}}&followingOnly={{feedFollowingOnly}}&radiusKm={{feedRadiusKm}}" + }, + "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.items).to.be.an('array');", + "if (json.items && json.items.length > 0) {", + " pm.expect(json.items[0]).to.have.property('feedItemType');", + "}", + "const hasMixedItems = (json.items || []).some(item => item.feedItemType === 'suggested_users' || item.feedItemType === 'featured_marketplace');", + "pm.environment.set('homeFeedHasMixedItems', hasMixedItems ? 'true' : 'false');" + ] + } + } + ] + }, + { + "name": "Get My Feed", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/feed/me?limit=20&cursor={{feedCursor}}&followingOnly={{feedFollowingOnly}}&radiusKm={{feedRadiusKm}}" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "const firstPost = (json.items || []).find(item => item.feedItemType === 'post');", + "if (firstPost) {", + " pm.expect(firstPost).to.have.property('likedByMe');", + " pm.expect(firstPost).to.have.property('savedByMe');", + " pm.expect(firstPost).to.have.property('engagement');", + "}", + "pm.environment.set('feedCursor', json.nextCursor || '');" + ] + } + } + ] + }, + { + "name": "Get My Feed Preferred Type", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/feed/me?limit=20&cursor={{feedCursor}}&preferredPostType={{feedPreferredPostType}}&followingOnly={{feedFollowingOnly}}&radiusKm={{feedRadiusKm}}" + }, + "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.pagination).to.exist;", + "const firstPost = (json.items || []).find(item => item.feedItemType === 'post');", + "if (firstPost) { pm.expect(firstPost).to.have.property('engagement'); }", + "pm.environment.set('feedCursor', json.nextCursor || '');" + ] + } + } + ] + }, + { + "name": "Get Trending Feed", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/feed/trending?limit=20&cursor={{feedCursor}}&preferredPostType={{feedPreferredPostType}}" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "const firstPost = (json.items || []).find(item => item.feedItemType === 'post');", + "if (firstPost) {", + " pm.expect(firstPost).to.have.property('likedByMe');", + " pm.expect(firstPost).to.have.property('savedByMe');", + "}", + "pm.environment.set('feedCursor', json.nextCursor || '');" + ] + } + } + ] + }, + { + "name": "Explore Feed", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/feed/explore?page=1&limit=20" + }, + "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.items).to.be.an('array');" + ] + } + } + ] + } + ] + }, + { + "name": "Smoke", + "item": [ + { + "name": "Login (Smoke)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/auth/login", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"{{registerEmail}}\",\n \"password\": \"StrongPass123!\"\n}" + } + }, + "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();", + "const uid = json.user._id || json.user.id;", + "pm.environment.set('accessToken', json.accessToken);", + "pm.environment.set('refreshToken', json.refreshToken);", + "pm.environment.set('userId', uid);" + ] + } + } + ] + }, + { + "name": "Get User Posts (Smoke)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/posts/user/{{userId}}?page=1&limit=10", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ] + } + }, + { + "name": "Get My Feed (Smoke)", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/feed/me?limit=20&cursor={{feedCursor}}&radiusKm=30" + } + } + ] + }, + { + "name": "Full E2E", + "item": [ + { + "name": "Create Post (Own) - E2E", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "E2E text post", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + } + ] + } + } + }, + { + "name": "Create Comment - E2E", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/comments", + "body": { + "mode": "raw", + "raw": "{\n \"postId\": \"{{postId}}\",\n \"content\": \"E2E comment\"\n}" + } + } + }, + { + "name": "Like Post - E2E", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/likes", + "body": { + "mode": "raw", + "raw": "{\n \"targetId\": \"{{postId}}\",\n \"targetType\": \"post\"\n}" + } + } + }, + { + "name": "Save Post - E2E", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/saves", + "body": { + "mode": "raw", + "raw": "{\n \"postId\": \"{{postId}}\"\n}" + } + } + }, + { + "name": "Delete Post - E2E Cleanup", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/{{postId}}" + } + } + ] + }, + { + "name": "Negative Tests", + "item": [ + { + "name": "Create Invalid Post (Video+Audio) - Negative", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "Invalid post", + "type": "text" + }, + { + "key": "videoUrl", + "value": "https://cdn.example.com/video.mp4", + "type": "text" + }, + { + "key": "audioUrl", + "value": "https://cdn.example.com/audio.mp3", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 400', function () { pm.response.to.have.status(400); });" + ] + } + } + ] + }, + { + "name": "Create Post Without Token - Negative", + "request": { + "method": "POST", + "header": [], + "url": "{{baseUrl}}/posts", + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "content", + "value": "No token request", + "type": "text" + }, + { + "key": "visibility", + "value": "public", + "type": "text" + } + ] + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 401', function () { pm.response.to.have.status(401); });" + ] + } + } + ] + } + ] + }, + { + "name": "Cleanup", + "item": [ + { + "name": "Delete Own Comment (if exists)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/comments/{{ownCommentId}}" + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('ownCommentId')) { postman.setNextRequest('Delete Own Post (if exists)'); }" + ] + } + } + ] + }, + { + "name": "Delete Own Post (if exists)", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/posts/{{ownPostId}}" + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('ownPostId')) { postman.setNextRequest('Delete Instrument (if exists)'); }" + ] + } + } + ] + } + ] + }, + { + "name": "AI Music", + "item": [ + { + "name": "Generate Music From Text (Gemini)", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/media/ai/text-to-music", + "body": { + "mode": "raw", + "raw": "{\n \"prompt\": \"{{aiMusicPrompt}}\",\n \"durationSeconds\": {{aiMusicDuration}},\n \"seed\": {{aiMusicSeed}}\n}" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "if (!pm.environment.get('aiMusicPrompt')) { pm.environment.set('aiMusicPrompt', 'Calm oud melody with light percussion and emotional Arabic mood'); }", + "if (!pm.environment.get('aiMusicDuration')) { pm.environment.set('aiMusicDuration', '12'); }", + "if (!pm.environment.get('aiMusicSeed')) { pm.environment.set('aiMusicSeed', '42'); }" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });", + "const json = pm.response.json();", + "pm.expect(json.audioUrl).to.exist;", + "pm.expect(json.mimeType).to.exist;", + "pm.expect(json.sizeBytes).to.be.a('number');", + "pm.environment.set('generatedMusicUrl', json.audioUrl);" + ] + } + } + ] + } + ] + }, + { + "name": "Marketplace", + "item": [ + { + "name": "01 Marketplace Overview", + "item": [ + { + "name": "Public Marketplace Home", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/marketplace/home?listingsLimit=4&instrumentsLimit=4&repairShopsLimit=3&onlyActive=true" + }, + "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('categories');", + "pm.expect(json).to.have.property('summary');", + "pm.expect(json).to.have.property('featuredShops');", + "pm.expect(json.filters).to.have.property('listingCategories');", + "pm.expect(json.sections).to.have.property('listings');", + "pm.expect(json.sections).to.have.property('musicalInstruments');", + "pm.expect(json.sections).to.have.property('repairShops');" + ] + } + } + ] + } + ] + }, + { + "name": "03 Repair Shops", + "item": [ + { + "name": "Public", + "item": [ + { + "name": "Public List Repair Shops", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/marketplace/repair-shops?q={{repairShopQuery}}&isActive={{repairShopIsActive}}&sortBy={{repairShopSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('repairShopId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Public Get Repair Shop By Id", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/marketplace/repair-shops/{{repairShopId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + } + ] + } + ] + }, + { + "name": "04 Musical Instruments", + "item": [ + { + "name": "Public", + "item": [ + { + "name": "Public List Instruments", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/marketplace/instruments?q={{marketplaceQuery}}&minPrice={{marketplaceMinPrice}}&maxPrice={{marketplaceMaxPrice}}&isActive={{marketplaceIsActive}}&condition={{marketplaceCondition}}&instrumentType={{marketplaceInstrumentType}}&sortBy={{marketplaceSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('instrumentId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Public Get Instrument By Id", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/marketplace/instruments/{{instrumentId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + } + ] + } + ] + }, + { + "name": "05 General Marketplace", + "item": [ + { + "name": "Public", + "item": [ + { + "name": "Public List Listings", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/marketplace/listings?q={{marketplaceQuery}}&minPrice={{marketplaceMinPrice}}&maxPrice={{marketplaceMaxPrice}}&isActive={{marketplaceIsActive}}&listingCategory={{marketplaceListingCategory}}&condition={{marketplaceCondition}}&instrumentType={{marketplaceInstrumentType}}&sortBy={{marketplaceSortBy}}&sortOrder={{listSortOrder}}&page=1&limit=20" + }, + "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.items).to.be.an('array');", + "pm.expect(json.pagination).to.exist;", + "if (json.items && json.items.length > 0) { pm.environment.set('listingId', json.items[0]._id || json.items[0].id); }" + ] + } + } + ] + }, + { + "name": "Public Get Listing By Id", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/marketplace/listings/{{listingId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + } + ] + } + ] + } + ] + }, + { + "name": "Blocks", + "item": [ + { + "name": "Block User", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/blocks/{{targetUserId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 201', function () { pm.response.to.have.status(201); });" + ] + } + } + ] + }, + { + "name": "Block Status", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/blocks/status/{{targetUserId}}" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Unblock User", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/blocks/{{targetUserId}}/unblock" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + } + ] + }, + { + "name": "Reports", + "item": [ + { + "name": "Report Post", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/reports", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "reports" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"targetType\": \"post\",\n \"targetId\": \"{{postId}}\",\n \"reason\": \"spam\",\n \"details\": \"This content looks suspicious\"\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();", + "const value = json.item;", + "if (value) pm.environment.set(\"reportId\", value._id || value.id || value);" + ] + } + } + ] + }, + { + "name": "My Reports", + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + } + ], + "url": "{{baseUrl}}/reports/me?page=1&limit=20" + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status is 200', function () { pm.response.to.have.status(200); });" + ] + } + } + ] + }, + { + "name": "Report Post - Fixed Reason", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/reports", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "reports" + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"targetType\": \"post\",\n \"targetId\": \"{{postId}}\",\n \"reason\": \"{{reportReason}}\",\n \"details\": \"Reported from Postman\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Report User And Block", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/reports", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "reports" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"targetType\": \"user\",\n \"targetId\": \"{{targetUserId}}\",\n \"reason\": \"harassment\",\n \"details\": \"Unsafe interaction\",\n \"blockTarget\": true\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();", + "const value = json.item;", + "if (value) pm.environment.set(\"reportId\", value._id || value.id || value);" + ] + } + } + ] + }, + { + "name": "Report Comment", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/reports", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "reports" + ], + "query": [] + }, + "body": { + "mode": "raw", + "raw": "{\n \"targetType\": \"comment\",\n \"targetId\": \"{{commentId}}\",\n \"reason\": \"harassment\",\n \"details\": \"Offensive comment\"\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();", + "const value = json.item;", + "if (value) pm.environment.set(\"reportId\", value._id || value.id || value);" + ] + } + } + ] + } + ] + }, + { + "name": "Health", + "item": [ + { + "name": "Health Check", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/", + "host": [ + "{{baseUrl}}" + ], + "path": [] + } + } + } + ] + }, + { + "name": "Devices", + "item": [ + { + "name": "Register Device FCM Token", + "request": { + "method": "POST", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/devices/register", + "body": { + "mode": "raw", + "raw": "{\n \"fcmToken\": \"{{fcmToken}}\",\n \"platform\": \"android\",\n \"deviceId\": \"{{deviceId}}\",\n \"appVersion\": \"1.0.0\",\n \"locale\": \"ar\"\n}" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Device registered', function () { const json = pm.response.json(); pm.expect(json.message).to.eql('Device registered successfully'); pm.expect(json).to.have.property('device'); });" + ] + } + } + }, + { + "name": "Unregister Device FCM Token", + "request": { + "method": "POST", + "header": { + "key": "Content-Type", + "value": "application/json" + }, + "url": "{{baseUrl}}/devices/unregister", + "body": { + "mode": "raw", + "raw": "{\n \"fcmToken\": \"{{fcmToken}}\"\n}" + } + }, + "event": { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Device unregistered', function () { const json = pm.response.json(); pm.expect(json.message).to.eql('Device unregistered successfully'); });" + ] + } + } + } + ] + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:4000/api/v1", + "type": "string" + }, + { + "key": "accessToken", + "value": "", + "type": "string" + }, + { + "key": "refreshToken", + "value": "", + "type": "string" + }, + { + "key": "superAdminAccessToken", + "value": "", + "type": "string" + }, + { + "key": "superAdminRefreshToken", + "value": "", + "type": "string" + }, + { + "key": "targetAccessToken", + "value": "", + "type": "string" + }, + { + "key": "targetRefreshToken", + "value": "", + "type": "string" + }, + { + "key": "userId", + "value": "", + "type": "string" + }, + { + "key": "currentUserId", + "value": "", + "type": "string" + }, + { + "key": "targetUserId", + "value": "", + "type": "string" + }, + { + "key": "targetUsername", + "value": "", + "type": "string" + }, + { + "key": "lastFollowingState", + "value": "", + "type": "string" + }, + { + "key": "notificationUnreadCount", + "value": "", + "type": "string" + }, + { + "key": "notificationId", + "value": "", + "type": "string" + }, + { + "key": "postId", + "value": "", + "type": "string" + }, + { + "key": "ownPostId", + "value": "", + "type": "string" + }, + { + "key": "targetPostId", + "value": "", + "type": "string" + }, + { + "key": "adminPostId", + "value": "", + "type": "string" + }, + { + "key": "commentId", + "value": "", + "type": "string" + }, + { + "key": "ownCommentId", + "value": "", + "type": "string" + }, + { + "key": "replyCommentId", + "value": "", + "type": "string" + }, + { + "key": "conversationId", + "value": "", + "type": "string" + }, + { + "key": "messageId", + "value": "", + "type": "string" + }, + { + "key": "sessionJti", + "value": "", + "type": "string" + }, + { + "key": "registerEmail", + "value": "", + "type": "string" + }, + { + "key": "registerUsername", + "value": "", + "type": "string" + }, + { + "key": "adminUserId", + "value": "", + "type": "string" + }, + { + "key": "targetLoginEmail", + "value": "", + "type": "string" + }, + { + "key": "targetLoginPassword", + "value": "", + "type": "string" + }, + { + "key": "resetCode", + "value": "", + "type": "string" + }, + { + "key": "resetToken", + "value": "", + "type": "string" + }, + { + "key": "emailVerificationCode", + "value": "", + "type": "string" + }, + { + "key": "googleIdToken", + "value": "", + "type": "string" + }, + { + "key": "newPassword", + "value": "NewStrongPass123!", + "type": "string" + }, + { + "key": "feedCursor", + "value": "", + "type": "string" + }, + { + "key": "chatConversationsCursor", + "value": "", + "type": "string" + }, + { + "key": "chatMessagesCursor", + "value": "", + "type": "string" + }, + { + "key": "adminAccessToken", + "value": "", + "type": "string" + }, + { + "key": "adminRefreshToken", + "value": "", + "type": "string" + }, + { + "key": "adminEmail", + "value": "store_admin@example.com", + "type": "string" + }, + { + "key": "adminUsername", + "value": "store_admin_01", + "type": "string" + }, + { + "key": "adminPassword", + "value": "AdminStrongPass123!", + "type": "string" + }, + { + "key": "usersQuery", + "value": "user", + "type": "string" + }, + { + "key": "usersVerified", + "value": "true", + "type": "string" + }, + { + "key": "usersSortBy", + "value": "createdAt", + "type": "string" + }, + { + "key": "listSortOrder", + "value": "desc", + "type": "string" + }, + { + "key": "commentSortBy", + "value": "createdAt", + "type": "string" + }, + { + "key": "postVisibility", + "value": "public", + "type": "string" + }, + { + "key": "postSortBy", + "value": "createdAt", + "type": "string" + }, + { + "key": "postTypeFilter", + "value": "image", + "type": "string" + }, + { + "key": "postSearchQuery", + "value": "music", + "type": "string" + }, + { + "key": "postHashtag", + "value": "music", + "type": "string" + }, + { + "key": "reelQuery", + "value": "reel", + "type": "string" + }, + { + "key": "notificationRead", + "value": "false", + "type": "string" + }, + { + "key": "notificationType", + "value": "mention", + "type": "string" + }, + { + "key": "notificationResourceType", + "value": "post", + "type": "string" + }, + { + "key": "feedPreferredPostType", + "value": "video", + "type": "string" + }, + { + "key": "feedIncludeSuggestions", + "value": "true", + "type": "string" + }, + { + "key": "feedSuggestionInterval", + "value": "4", + "type": "string" + }, + { + "key": "feedFollowingOnly", + "value": "false", + "type": "string" + }, + { + "key": "feedRadiusKm", + "value": "30", + "type": "string" + }, + { + "key": "homeFeedHasMixedItems", + "value": "", + "type": "string" + }, + { + "key": "lastViewCount", + "value": "", + "type": "string" + }, + { + "key": "lastPlayCount", + "value": "", + "type": "string" + }, + { + "key": "lastShareCount", + "value": "", + "type": "string" + }, + { + "key": "marketplaceQuery", + "value": "oud", + "type": "string" + }, + { + "key": "marketplaceMinPrice", + "value": "1000", + "type": "string" + }, + { + "key": "marketplaceMaxPrice", + "value": "5000", + "type": "string" + }, + { + "key": "marketplaceIsActive", + "value": "true", + "type": "string" + }, + { + "key": "marketplaceSortBy", + "value": "price", + "type": "string" + }, + { + "key": "repairShopQuery", + "value": "riyadh", + "type": "string" + }, + { + "key": "repairShopIsActive", + "value": "true", + "type": "string" + }, + { + "key": "repairShopSortBy", + "value": "name", + "type": "string" + }, + { + "key": "instrumentId", + "value": "", + "type": "string" + }, + { + "key": "repairShopId", + "value": "", + "type": "string" + }, + { + "key": "generatedMusicUrl", + "value": "", + "type": "string" + }, + { + "key": "aiMusicPrompt", + "value": "Calm oud melody with light percussion and emotional Arabic mood", + "type": "string" + }, + { + "key": "aiMusicDuration", + "value": "12", + "type": "string" + }, + { + "key": "aiMusicSeed", + "value": "42", + "type": "string" + }, + { + "key": "listingId", + "value": "", + "type": "string" + }, + { + "key": "marketplaceListingCategory", + "value": "accessory", + "type": "string" + }, + { + "key": "marketplaceCondition", + "value": "used", + "type": "string" + }, + { + "key": "marketplaceInstrumentType", + "value": "oud", + "type": "string" + }, + { + "key": "talentRole", + "value": "instrumentalist", + "type": "string" + }, + { + "key": "talentExperienceLevel", + "value": "", + "type": "string" + }, + { + "key": "usersHasAvatar", + "value": "true", + "type": "string" + }, + { + "key": "reactionType", + "value": "love", + "type": "string" + }, + { + "key": "followRequestId", + "value": "", + "type": "string" + }, + { + "key": "reportId", + "value": "", + "type": "string" + }, + { + "key": "repostId", + "value": "", + "type": "string" + }, + { + "key": "quotePostId", + "value": "", + "type": "string" + }, + { + "key": "replyToMessageId", + "value": "" + }, + { + "key": "mediaFilePath", + "value": "" + }, + { + "key": "collaboratorId", + "value": "" + }, + { + "key": "commentKeyword", + "value": "spam" + }, + { + "key": "reportReason", + "value": "spam" + }, + { + "key": "sessionId", + "value": "" + }, + { + "key": "caseId", + "value": "" + }, + { + "key": "historyId", + "value": "" + }, + { + "key": "bulkActionTargetId", + "value": "" + }, + { + "key": "auditAction", + "value": "" + }, + { + "key": "searchQuery", + "value": "music" + }, + { + "key": "searchType", + "value": "all" + }, + { + "key": "searchLimit", + "value": "20" + }, + { + "key": "searchSuggestionLimit", + "value": "5" + }, + { + "key": "collaborationType", + "value": "duet" + }, + { + "key": "collaborationMessage", + "value": "I want to collaborate on this piece" + }, + { + "key": "collaborationAttachmentUrl", + "value": "" + }, + { + "key": "collaborationAttachmentType", + "value": "audio" + } + ] } diff --git a/src/app.module.ts b/src/app.module.ts index af513a5..7906697 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -28,6 +28,7 @@ import { OutboxModule } from './modules/outbox/outbox.module'; import { PostsModule } from './modules/posts/posts.module'; import { ReportsModule } from './modules/reports/reports.module'; import { SavesModule } from './modules/saves/saves.module'; +import { SearchModule } from './modules/search/search.module'; import { SuperAdminModule } from './modules/superadmin/superadmin.module'; import { UsersModule } from './modules/users/users.module'; import { ThrottleGuard } from './common/guards/throttle.guard'; @@ -64,6 +65,7 @@ import { ThrottleGuard } from './common/guards/throttle.guard'; MarketplaceModule, ReportsModule, SavesModule, + SearchModule, SuperAdminModule, ], controllers: [AppController], diff --git a/src/modules/collaboration-requests/collaboration-requests.service.ts b/src/modules/collaboration-requests/collaboration-requests.service.ts index be7d974..1cdaae7 100644 --- a/src/modules/collaboration-requests/collaboration-requests.service.ts +++ b/src/modules/collaboration-requests/collaboration-requests.service.ts @@ -1,4 +1,9 @@ -import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; +import { + BadRequestException, + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; import { PaginationQueryDto } from '../../common/dto/pagination-query.dto'; @@ -7,6 +12,7 @@ import { BlocksRepository } from '../blocks/blocks.repository'; import { NotificationsService } from '../notifications/notifications.service'; import { PostsRepository } from '../posts/posts.repository'; import { UsersRepository } from '../users/users.repository'; +import { CreateCollaborationRequestDto } from './dto/create-collaboration-request.dto'; import { CollaborationRequest, CollaborationRequestDocument, @@ -23,7 +29,8 @@ export class CollaborationRequestsService { private readonly notificationsService: NotificationsService, ) {} - async create(requesterId: string, postId: string, targetUserId: string) { + async create(requesterId: string, postId: string, dto: CreateCollaborationRequestDto) { + const targetUserId = dto.targetUserId; if (!Types.ObjectId.isValid(postId) || !Types.ObjectId.isValid(targetUserId)) { throw new BadRequestException('Invalid collaboration request'); } @@ -55,21 +62,25 @@ export class CollaborationRequestsService { targetUserId: new Types.ObjectId(targetUserId), status: 'pending', }; + const collaborationDetails = this.buildCollaborationDetails(dto); const existing = await this.collaborationRequestModel.findOne(filter).exec(); - const request = existing ?? await this.collaborationRequestModel - .findOneAndUpdate( - filter, - { - $setOnInsert: { - postId: new Types.ObjectId(postId), - requesterId: new Types.ObjectId(requesterId), - targetUserId: new Types.ObjectId(targetUserId), - status: 'pending', - }, - }, - { new: true, upsert: true, setDefaultsOnInsert: true }, - ) - .exec(); + const request = existing + ? await this.updateExistingPendingRequest(existing.id, collaborationDetails) + : await this.collaborationRequestModel + .findOneAndUpdate( + filter, + { + $setOnInsert: { + postId: new Types.ObjectId(postId), + requesterId: new Types.ObjectId(requesterId), + targetUserId: new Types.ObjectId(targetUserId), + status: 'pending', + ...collaborationDetails, + }, + }, + { new: true, upsert: true, setDefaultsOnInsert: true }, + ) + .exec(); if (!existing) { await this.notificationsService.create({ @@ -79,6 +90,7 @@ export class CollaborationRequestsService { referenceId: postId, resourceType: 'post', deepLink: `/posts/${postId}`, + metadata: collaborationDetails, }); } @@ -93,7 +105,10 @@ export class CollaborationRequestsService { const [items, total] = await Promise.all([ this.collaborationRequestModel .find(filter) - .populate({ path: 'requesterId', select: 'name username stageName avatar isVerified isDisabled' }) + .populate({ + path: 'requesterId', + select: 'name username stageName avatar isVerified isDisabled', + }) .populate({ path: 'postId' }) .sort({ createdAt: -1 }) .skip(skip) @@ -117,13 +132,43 @@ export class CollaborationRequestsService { return { rejected: true, request }; } - private async updateStatus(targetUserId: string, requestId: string, status: 'approved' | 'rejected') { + private buildCollaborationDetails(dto: CreateCollaborationRequestDto) { + return { + ...(dto.collaborationType ? { collaborationType: dto.collaborationType } : {}), + ...(typeof dto.message === 'string' ? { message: dto.message.trim() } : {}), + ...(typeof dto.attachmentUrl === 'string' ? { attachmentUrl: dto.attachmentUrl.trim() } : {}), + ...(dto.attachmentType ? { attachmentType: dto.attachmentType } : {}), + }; + } + + private async updateExistingPendingRequest( + requestId: string, + collaborationDetails: Record, + ) { + if (!Object.keys(collaborationDetails).length) { + return this.collaborationRequestModel.findById(requestId).exec(); + } + + return this.collaborationRequestModel + .findByIdAndUpdate(requestId, { $set: collaborationDetails }, { new: true }) + .exec(); + } + + private async updateStatus( + targetUserId: string, + requestId: string, + status: 'approved' | 'rejected', + ) { if (!Types.ObjectId.isValid(requestId)) { throw new BadRequestException('Invalid collaboration request id'); } const request = await this.collaborationRequestModel .findOneAndUpdate( - { _id: new Types.ObjectId(requestId), targetUserId: new Types.ObjectId(targetUserId), status: 'pending' }, + { + _id: new Types.ObjectId(requestId), + targetUserId: new Types.ObjectId(targetUserId), + status: 'pending', + }, { status }, { new: true }, ) diff --git a/src/modules/collaboration-requests/dto/create-collaboration-request.dto.ts b/src/modules/collaboration-requests/dto/create-collaboration-request.dto.ts index 6af9d68..a294d2a 100644 --- a/src/modules/collaboration-requests/dto/create-collaboration-request.dto.ts +++ b/src/modules/collaboration-requests/dto/create-collaboration-request.dto.ts @@ -1,6 +1,35 @@ -import { IsMongoId } from 'class-validator'; +import { Transform } from 'class-transformer'; +import { IsEnum, IsMongoId, IsOptional, IsString, MaxLength } from 'class-validator'; +import { + COLLABORATION_ATTACHMENT_TYPES, + COLLABORATION_TYPES, + CollaborationAttachmentType, + CollaborationType, +} from '../schemas/collaboration-request.schema'; export class CreateCollaborationRequestDto { @IsMongoId() targetUserId!: string; + + @IsOptional() + @Transform(({ value }) => (typeof value === 'string' ? value.trim().toLowerCase() : value)) + @IsEnum(COLLABORATION_TYPES) + collaborationType?: CollaborationType; + + @IsOptional() + @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value)) + @IsString() + @MaxLength(1000) + message?: string; + + @IsOptional() + @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value)) + @IsString() + @MaxLength(500) + attachmentUrl?: string; + + @IsOptional() + @Transform(({ value }) => (typeof value === 'string' ? value.trim().toLowerCase() : value)) + @IsEnum(COLLABORATION_ATTACHMENT_TYPES) + attachmentType?: CollaborationAttachmentType; } diff --git a/src/modules/collaboration-requests/posts-collaboration-requests.controller.ts b/src/modules/collaboration-requests/posts-collaboration-requests.controller.ts index 2a0f2ee..300d473 100644 --- a/src/modules/collaboration-requests/posts-collaboration-requests.controller.ts +++ b/src/modules/collaboration-requests/posts-collaboration-requests.controller.ts @@ -19,6 +19,6 @@ export class PostsCollaborationRequestsController { @Param('postId') postId: string, @Body() dto: CreateCollaborationRequestDto, ) { - return this.collaborationRequestsService.create(user.sub, postId, dto.targetUserId); + return this.collaborationRequestsService.create(user.sub, postId, dto); } } diff --git a/src/modules/collaboration-requests/schemas/collaboration-request.schema.ts b/src/modules/collaboration-requests/schemas/collaboration-request.schema.ts index 8594878..f11ca87 100644 --- a/src/modules/collaboration-requests/schemas/collaboration-request.schema.ts +++ b/src/modules/collaboration-requests/schemas/collaboration-request.schema.ts @@ -4,6 +4,11 @@ import { Post } from '../../posts/schemas/post.schema'; import { User } from '../../users/schemas/user.schema'; export type CollaborationRequestDocument = HydratedDocument; +export const COLLABORATION_TYPES = ['duet', 'arrangement', 'composition'] as const; +export type CollaborationType = (typeof COLLABORATION_TYPES)[number]; + +export const COLLABORATION_ATTACHMENT_TYPES = ['audio', 'demo', 'file'] as const; +export type CollaborationAttachmentType = (typeof COLLABORATION_ATTACHMENT_TYPES)[number]; @Schema({ timestamps: true, versionKey: false }) export class CollaborationRequest { @@ -18,6 +23,18 @@ export class CollaborationRequest { @Prop({ enum: ['pending', 'approved', 'rejected'], default: 'pending', index: true }) status!: 'pending' | 'approved' | 'rejected'; + + @Prop({ enum: COLLABORATION_TYPES, default: null, index: true }) + collaborationType?: CollaborationType | null; + + @Prop({ default: '', trim: true, maxlength: 1000 }) + message!: string; + + @Prop({ default: '', trim: true, maxlength: 500 }) + attachmentUrl!: string; + + @Prop({ enum: COLLABORATION_ATTACHMENT_TYPES, default: null }) + attachmentType?: CollaborationAttachmentType | null; } export const CollaborationRequestSchema = SchemaFactory.createForClass(CollaborationRequest); diff --git a/src/modules/posts/schemas/post.schema.ts b/src/modules/posts/schemas/post.schema.ts index 90adfa4..de9b607 100644 --- a/src/modules/posts/schemas/post.schema.ts +++ b/src/modules/posts/schemas/post.schema.ts @@ -201,6 +201,16 @@ PostSchema.index({ authorId: 1, isArchived: 1, createdAt: -1 }); PostSchema.index({ moderationStatus: 1, createdAt: -1 }); PostSchema.index({ authorId: 1, isDeleted: 1, createdAt: -1 }); PostSchema.index({ visibility: 1, isDeleted: 1, createdAt: -1 }); +PostSchema.index( + { + content: 'text', + hashtags: 'text', + style: 'text', + maqam: 'text', + rhythmSignature: 'text', + }, + { name: 'post_search_text' }, +); PostSchema.index({ visibility: 1, isDeleted: 1, diff --git a/src/modules/search/dto/search-query.dto.ts b/src/modules/search/dto/search-query.dto.ts new file mode 100644 index 0000000..c035105 --- /dev/null +++ b/src/modules/search/dto/search-query.dto.ts @@ -0,0 +1,57 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsIn, IsInt, IsOptional, IsString, Max, MaxLength, Min } from 'class-validator'; +import { SortOrder } from '../../../common/enums/sort-order.enum'; + +export type SearchType = 'all' | 'users' | 'posts' | 'hashtags'; + +export class SearchQueryDto { + @ApiPropertyOptional({ description: 'Search text', minLength: 1, maxLength: 100 }) + @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value)) + @IsString() + @MaxLength(100) + q!: string; + + @ApiPropertyOptional({ enum: ['all', 'users', 'posts', 'hashtags'], default: 'all' }) + @IsOptional() + @Transform(({ value }) => (typeof value === 'string' ? value.trim().toLowerCase() : value)) + @IsIn(['all', 'users', 'posts', 'hashtags']) + type?: SearchType = 'all'; + + @ApiPropertyOptional({ default: 1 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + page?: number = 1; + + @ApiPropertyOptional({ default: 20, maximum: 50 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(50) + limit?: number = 20; + + @ApiPropertyOptional({ enum: SortOrder, default: SortOrder.DESC }) + @IsOptional() + @Transform(({ value }) => (typeof value === 'string' ? value.trim().toLowerCase() : value)) + @IsIn([SortOrder.ASC, SortOrder.DESC]) + sortOrder?: SortOrder = SortOrder.DESC; +} + +export class SearchSuggestionsQueryDto { + @ApiPropertyOptional({ description: 'Search text', minLength: 1, maxLength: 100 }) + @Transform(({ value }) => (typeof value === 'string' ? value.trim() : value)) + @IsString() + @MaxLength(100) + q!: string; + + @ApiPropertyOptional({ default: 5, maximum: 20 }) + @IsOptional() + @Type(() => Number) + @IsInt() + @Min(1) + @Max(20) + limit?: number = 5; +} diff --git a/src/modules/search/search.controller.ts b/src/modules/search/search.controller.ts new file mode 100644 index 0000000..f0bc8d6 --- /dev/null +++ b/src/modules/search/search.controller.ts @@ -0,0 +1,40 @@ +import { Controller, Get, Query, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { CurrentUser } from '../../common/decorators/current-user.decorator'; +import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; +import { JwtPayload } from '../../common/interfaces/jwt-payload.interface'; +import { SearchQueryDto, SearchSuggestionsQueryDto } from './dto/search-query.dto'; +import { SearchService } from './search.service'; + +@ApiTags('Search') +@ApiBearerAuth() +@UseGuards(JwtAuthGuard) +@Controller('search') +export class SearchController { + constructor(private readonly searchService: SearchService) {} + + @Get() + async globalSearch(@CurrentUser() user: JwtPayload, @Query() query: SearchQueryDto) { + return this.searchService.globalSearch(user.sub, query); + } + + @Get('users') + async searchUsers(@CurrentUser() user: JwtPayload, @Query() query: SearchQueryDto) { + return this.searchService.searchUsers(user.sub, query); + } + + @Get('posts') + async searchPosts(@CurrentUser() user: JwtPayload, @Query() query: SearchQueryDto) { + return this.searchService.searchPosts(user.sub, query); + } + + @Get('hashtags') + async searchHashtags(@CurrentUser() user: JwtPayload, @Query() query: SearchQueryDto) { + return this.searchService.searchHashtags(user.sub, query); + } + + @Get('suggestions') + async suggestions(@CurrentUser() user: JwtPayload, @Query() query: SearchSuggestionsQueryDto) { + return this.searchService.getSuggestions(user.sub, query); + } +} diff --git a/src/modules/search/search.module.ts b/src/modules/search/search.module.ts new file mode 100644 index 0000000..e438c5f --- /dev/null +++ b/src/modules/search/search.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { BlocksModule } from '../blocks/blocks.module'; +import { Post, PostSchema } from '../posts/schemas/post.schema'; +import { User, UserSchema } from '../users/schemas/user.schema'; +import { SearchController } from './search.controller'; +import { SearchService } from './search.service'; + +@Module({ + imports: [ + BlocksModule, + MongooseModule.forFeature([ + { name: User.name, schema: UserSchema }, + { name: Post.name, schema: PostSchema }, + ]), + ], + controllers: [SearchController], + providers: [SearchService], +}) +export class SearchModule {} diff --git a/src/modules/search/search.service.ts b/src/modules/search/search.service.ts new file mode 100644 index 0000000..ac6c8f6 --- /dev/null +++ b/src/modules/search/search.service.ts @@ -0,0 +1,414 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectConnection, InjectModel } from '@nestjs/mongoose'; +import { Connection, FilterQuery, Model, PipelineStage, Types } from 'mongoose'; +import { ModerationStatus } from '../../common/enums/moderation-status.enum'; +import { PostVisibility } from '../../common/enums/post-visibility.enum'; +import { buildPaginatedResponse, PaginatedResponse } from '../../common/utils/pagination.util'; +import { resolveMongoSortDirection } from '../../common/utils/sort.util'; +import { BlocksRepository } from '../blocks/blocks.repository'; +import { Post, PostDocument } from '../posts/schemas/post.schema'; +import { User, UserDocument } from '../users/schemas/user.schema'; +import { SearchQueryDto, SearchSuggestionsQueryDto, SearchType } from './dto/search-query.dto'; + +type SearchUserItem = { + _id: string; + name: string; + stageName: string; + username: string; + avatar: string; + isVerified: boolean; + isDisabled: boolean; + followersCount: number; + followingCount: number; + isFollowing: boolean; +}; + +type HashtagSearchItem = { + tag: string; + postsCount: number; +}; + +const emptyPage = (page: number, limit: number): PaginatedResponse => + buildPaginatedResponse([], { page, limit, total: 0, offset: (page - 1) * limit }); + +@Injectable() +export class SearchService { + constructor( + @InjectConnection() private readonly connection: Connection, + @InjectModel(User.name) private readonly userModel: Model, + @InjectModel(Post.name) private readonly postModel: Model, + private readonly blocksRepository: BlocksRepository, + ) {} + + async globalSearch(currentUserId: string, query: SearchQueryDto) { + const normalized = this.normalizeSearchText(query.q); + const type = query.type ?? 'all'; + const page = query.page ?? 1; + const limit = query.limit ?? 20; + + const [users, posts, hashtags] = await Promise.all([ + type === 'all' || type === 'users' + ? this.searchUsers(currentUserId, { ...query, q: normalized }) + : Promise.resolve(emptyPage(page, limit)), + type === 'all' || type === 'posts' + ? this.searchPosts(currentUserId, { ...query, q: normalized }) + : Promise.resolve(emptyPage(page, limit)), + type === 'all' || type === 'hashtags' + ? this.searchHashtags(currentUserId, { ...query, q: normalized }) + : Promise.resolve(emptyPage(page, limit)), + ]); + + return { + query: normalized, + type, + users, + posts, + hashtags, + }; + } + + async searchUsers( + currentUserId: string, + query: SearchQueryDto, + ): Promise> { + const normalized = this.normalizeSearchText(query.q); + const page = query.page ?? 1; + const limit = query.limit ?? 20; + const skip = (page - 1) * limit; + const regex = this.safeRegex(normalized); + const excludedIds = await this.getBlockedOrBlockingObjectIds(currentUserId); + + const filter: FilterQuery = { + isDisabled: false, + ...(excludedIds.length ? { _id: { $nin: excludedIds } } : {}), + $or: [{ username: regex }, { name: regex }, { stageName: regex }], + }; + + const [users, total] = await Promise.all([ + this.userModel + .find(filter) + .sort({ isVerified: -1, followersCount: -1, username: 1 }) + .skip(skip) + .limit(limit) + .exec(), + this.userModel.countDocuments(filter).exec(), + ]); + + const followingSet = await this.getFollowingSet( + currentUserId, + users.map((user) => user.id), + ); + const items = users.map((user) => this.toUserItem(user, followingSet)); + + return buildPaginatedResponse(items, { page, limit, total, offset: skip }); + } + + async searchPosts(currentUserId: string, query: SearchQueryDto): Promise> { + const normalized = this.normalizeSearchText(query.q); + const page = query.page ?? 1; + const limit = query.limit ?? 20; + const skip = (page - 1) * limit; + const regex = this.safeRegex(normalized); + const [blockedIds, followingIds, activeAuthorIds] = await Promise.all([ + this.getBlockedOrBlockingObjectIds(currentUserId), + this.getFollowingObjectIds(currentUserId), + this.getActiveSearchableAuthorIds(currentUserId), + ]); + + const allowedAuthorIds = activeAuthorIds.filter( + (authorId) => !blockedIds.some((blockedId) => blockedId.equals(authorId)), + ); + const currentObjectId = new Types.ObjectId(currentUserId); + + const visibilityClauses: FilterQuery[] = [ + { visibility: PostVisibility.PUBLIC }, + { authorId: currentObjectId }, + ]; + if (followingIds.length) { + visibilityClauses.push({ + visibility: PostVisibility.FOLLOWERS, + authorId: { $in: followingIds }, + }); + } + + const filter: FilterQuery = { + isDeleted: { $ne: true }, + isArchived: { $ne: true }, + moderationStatus: { $ne: ModerationStatus.HIDDEN }, + authorId: { $in: allowedAuthorIds }, + $and: [ + { $or: visibilityClauses }, + { + $or: [ + { content: regex }, + { hashtags: regex }, + { style: regex }, + { maqam: regex }, + { rhythmSignature: regex }, + ], + }, + ], + }; + + const sortDirection = resolveMongoSortDirection(query.sortOrder); + const [posts, total] = await Promise.all([ + this.postModel + .find(filter) + .populate({ + path: 'authorId', + select: 'name username avatar isVerified stageName isDisabled', + }) + .populate({ path: 'taggedUserIds', select: 'name username avatar stageName isVerified' }) + .populate({ path: 'collaboratorIds', select: 'name username avatar stageName isVerified' }) + .sort({ createdAt: sortDirection }) + .skip(skip) + .limit(limit) + .exec(), + this.postModel.countDocuments(filter).exec(), + ]); + + const postIds = posts.map((post) => post.id); + const authorIds = posts + .map((post) => this.extractEntityId((post as any).authorId)) + .filter(Boolean); + const [likedSet, savedSet, followingSet] = await Promise.all([ + this.getLikedPostSet(currentUserId, postIds), + this.getSavedPostSet(currentUserId, postIds), + this.getFollowingSet(currentUserId, authorIds), + ]); + + const items = posts.map((post) => { + const object = post.toObject(); + const authorId = this.extractEntityId(object.authorId); + + return { + ...object, + isLiked: likedSet.has(post.id), + liked: likedSet.has(post.id), + isSaved: savedSet.has(post.id), + saved: savedSet.has(post.id), + isFollowingAuthor: authorId ? followingSet.has(authorId) : false, + }; + }); + + return buildPaginatedResponse(items, { page, limit, total, offset: skip }); + } + + async searchHashtags( + currentUserId: string, + query: SearchQueryDto, + ): Promise> { + const normalized = this.normalizeHashtag(query.q); + const page = query.page ?? 1; + const limit = query.limit ?? 20; + const skip = (page - 1) * limit; + const regex = this.safeRegex(normalized); + const [blockedIds, followingIds, activeAuthorIds] = await Promise.all([ + this.getBlockedOrBlockingObjectIds(currentUserId), + this.getFollowingObjectIds(currentUserId), + this.getActiveSearchableAuthorIds(currentUserId), + ]); + const currentObjectId = new Types.ObjectId(currentUserId); + const allowedAuthorIds = activeAuthorIds.filter( + (authorId) => !blockedIds.some((blockedId) => blockedId.equals(authorId)), + ); + const visibilityOr: Record[] = [ + { visibility: PostVisibility.PUBLIC }, + { authorId: currentObjectId }, + ]; + if (followingIds.length) { + visibilityOr.push({ visibility: PostVisibility.FOLLOWERS, authorId: { $in: followingIds } }); + } + + const pipeline: PipelineStage[] = [ + { + $match: { + isDeleted: { $ne: true }, + isArchived: { $ne: true }, + moderationStatus: { $ne: ModerationStatus.HIDDEN }, + authorId: { $in: allowedAuthorIds }, + $or: visibilityOr, + hashtags: regex, + }, + }, + { $unwind: '$hashtags' }, + { $match: { hashtags: regex } }, + { $group: { _id: '$hashtags', postsCount: { $sum: 1 } } }, + { $sort: { postsCount: -1, _id: 1 } }, + { + $facet: { + items: [{ $skip: skip }, { $limit: limit }], + total: [{ $count: 'count' }], + }, + }, + ]; + + const [result] = await this.postModel.aggregate(pipeline).exec(); + const items = ((result?.items ?? []) as Array<{ _id: string; postsCount: number }>).map( + (item) => ({ + tag: item._id, + postsCount: item.postsCount, + }), + ); + const total = Number(result?.total?.[0]?.count ?? 0); + + return buildPaginatedResponse(items, { page, limit, total, offset: skip }); + } + + async getSuggestions(currentUserId: string, query: SearchSuggestionsQueryDto) { + const normalized = this.normalizeSearchText(query.q); + const limit = query.limit ?? 5; + const [users, hashtags] = await Promise.all([ + this.searchUsers(currentUserId, { q: normalized, page: 1, limit, type: 'users' }), + this.searchHashtags(currentUserId, { q: normalized, page: 1, limit, type: 'hashtags' }), + ]); + + return { + query: normalized, + users: users.items, + hashtags: hashtags.items, + }; + } + + private normalizeSearchText(value: string): string { + const normalized = (value ?? '').trim(); + if (!normalized) { + throw new BadRequestException('q is required'); + } + if (normalized.length > 100) { + throw new BadRequestException('q must be 100 characters or less'); + } + return normalized; + } + + private normalizeHashtag(value: string): string { + return this.normalizeSearchText(value).replace(/^#+/, '').toLowerCase(); + } + + private safeRegex(value: string): RegExp { + return new RegExp(this.escapeRegex(value), 'i'); + } + + private escapeRegex(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + private async getBlockedOrBlockingObjectIds(currentUserId: string): Promise { + const ids = await this.blocksRepository.findBlockingOrBlockedIds(currentUserId); + return ids.filter((id) => Types.ObjectId.isValid(id)).map((id) => new Types.ObjectId(id)); + } + + private async getFollowingObjectIds(currentUserId: string): Promise { + const rows = await this.connection + .collection('follows') + .find({ followerId: new Types.ObjectId(currentUserId) }) + .project({ followingId: 1 }) + .toArray(); + + return rows + .map((row) => row.followingId) + .filter((id): id is Types.ObjectId => id instanceof Types.ObjectId); + } + + private async getFollowingSet( + currentUserId: string, + targetUserIds: string[], + ): Promise> { + const normalized = Array.from( + new Set(targetUserIds.filter((id) => Types.ObjectId.isValid(id))), + ); + if (!normalized.length) { + return new Set(); + } + + const rows = await this.connection + .collection('follows') + .find({ + followerId: new Types.ObjectId(currentUserId), + followingId: { $in: normalized.map((id) => new Types.ObjectId(id)) }, + }) + .project({ followingId: 1 }) + .toArray(); + + return new Set(rows.map((row) => row.followingId?.toString()).filter(Boolean)); + } + + private async getLikedPostSet(currentUserId: string, postIds: string[]): Promise> { + return this.getViewerPostStateSet('likes', currentUserId, postIds, 'targetId', { + targetType: 'post', + }); + } + + private async getSavedPostSet(currentUserId: string, postIds: string[]): Promise> { + return this.getViewerPostStateSet('saves', currentUserId, postIds, 'postId'); + } + + private async getViewerPostStateSet( + collectionName: string, + currentUserId: string, + postIds: string[], + postField: 'targetId' | 'postId', + extraFilter: Record = {}, + ): Promise> { + const normalized = Array.from(new Set(postIds.filter((id) => Types.ObjectId.isValid(id)))); + if (!normalized.length) { + return new Set(); + } + + const rows = await this.connection + .collection(collectionName) + .find({ + userId: new Types.ObjectId(currentUserId), + [postField]: { $in: normalized.map((id) => new Types.ObjectId(id)) }, + ...extraFilter, + }) + .project({ [postField]: 1 }) + .toArray(); + + return new Set(rows.map((row) => row[postField]?.toString()).filter(Boolean)); + } + + private async getActiveSearchableAuthorIds(currentUserId: string): Promise { + const rows = await this.userModel.find({ isDisabled: false }).select({ _id: 1 }).lean().exec(); + const currentObjectId = new Types.ObjectId(currentUserId); + const ids = rows + .map((row) => row._id) + .filter((id): id is Types.ObjectId => id instanceof Types.ObjectId); + + if (!ids.some((id) => id.equals(currentObjectId))) { + ids.push(currentObjectId); + } + + return ids; + } + + private toUserItem(user: UserDocument, followingSet: Set): SearchUserItem { + const object = user.toObject(); + return { + _id: user.id, + name: object.name ?? '', + stageName: object.stageName ?? '', + username: object.username ?? '', + avatar: object.avatar ?? '', + isVerified: object.isVerified ?? false, + isDisabled: object.isDisabled ?? false, + followersCount: object.followersCount ?? 0, + followingCount: object.followingCount ?? 0, + isFollowing: followingSet.has(user.id), + }; + } + + private extractEntityId(value: unknown): string { + if (!value) { + return ''; + } + if (value instanceof Types.ObjectId) { + return value.toString(); + } + if (typeof value === 'object' && '_id' in value) { + const id = (value as { _id?: unknown })._id; + return id instanceof Types.ObjectId ? id.toString() : String(id ?? ''); + } + return String(value); + } +} diff --git a/src/modules/users/schemas/user.schema.ts b/src/modules/users/schemas/user.schema.ts index 9394d31..ee89ce6 100644 --- a/src/modules/users/schemas/user.schema.ts +++ b/src/modules/users/schemas/user.schema.ts @@ -18,7 +18,15 @@ export class User { @Prop({ default: '', trim: true, maxlength: 80, index: true }) stageName!: string; - @Prop({ required: true, trim: true, lowercase: true, unique: true, index: true, minlength: 3, maxlength: 30 }) + @Prop({ + required: true, + trim: true, + lowercase: true, + unique: true, + index: true, + minlength: 3, + maxlength: 30, + }) username!: string; @Prop({ required: true, trim: true, lowercase: true, unique: true, index: true }) @@ -127,6 +135,11 @@ export class User { export const UserSchema = SchemaFactory.createForClass(User); UserSchema.index({ createdAt: -1 }); +UserSchema.index({ name: 1, isDisabled: 1 }); +UserSchema.index( + { username: 'text', name: 'text', stageName: 'text' }, + { name: 'user_search_text' }, +); const stripLegacyRoleFlags = (_doc: unknown, ret: any) => { delete ret.isInstrumentalist;