Mark-Lasfar commited on
Commit ·
4dd49ba
1
Parent(s): fe9d07e
Add GraphQL
Browse files- package-lock.json +155 -0
- package.json +1 -0
- server.js +566 -45
- utils/socket-client.js +32 -0
package-lock.json
CHANGED
|
@@ -54,6 +54,7 @@
|
|
| 54 |
"passport-oauth2": "^1.8.0",
|
| 55 |
"pdf-parse": "^1.1.4",
|
| 56 |
"sharp": "^0.32.6",
|
|
|
|
| 57 |
"stripe": "^22.2.2",
|
| 58 |
"swagger-jsdoc": "^6.2.8",
|
| 59 |
"swagger-ui-express": "^5.0.1",
|
|
@@ -1719,6 +1720,12 @@
|
|
| 1719 |
"node": ">=18"
|
| 1720 |
}
|
| 1721 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1722 |
"node_modules/@tokenizer/inflate": {
|
| 1723 |
"version": "0.4.1",
|
| 1724 |
"resolved": "https://registry.npmmirror.com/@tokenizer/inflate/-/inflate-0.4.1.tgz",
|
|
@@ -3090,6 +3097,51 @@
|
|
| 3090 |
"once": "^1.4.0"
|
| 3091 |
}
|
| 3092 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3093 |
"node_modules/es-define-property": {
|
| 3094 |
"version": "1.0.1",
|
| 3095 |
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
@@ -6521,6 +6573,80 @@
|
|
| 6521 |
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
|
| 6522 |
"license": "MIT"
|
| 6523 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6524 |
"node_modules/sparse-bitfield": {
|
| 6525 |
"version": "3.0.3",
|
| 6526 |
"resolved": "https://registry.npmmirror.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
|
@@ -7431,6 +7557,27 @@
|
|
| 7431 |
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
| 7432 |
"license": "ISC"
|
| 7433 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7434 |
"node_modules/xml": {
|
| 7435 |
"version": "1.0.1",
|
| 7436 |
"resolved": "https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz",
|
|
@@ -7477,6 +7624,14 @@
|
|
| 7477 |
"node": ">=4.0"
|
| 7478 |
}
|
| 7479 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7480 |
"node_modules/xtend": {
|
| 7481 |
"version": "4.0.2",
|
| 7482 |
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
|
|
|
|
| 54 |
"passport-oauth2": "^1.8.0",
|
| 55 |
"pdf-parse": "^1.1.4",
|
| 56 |
"sharp": "^0.32.6",
|
| 57 |
+
"socket.io-client": "^4.8.3",
|
| 58 |
"stripe": "^22.2.2",
|
| 59 |
"swagger-jsdoc": "^6.2.8",
|
| 60 |
"swagger-ui-express": "^5.0.1",
|
|
|
|
| 1720 |
"node": ">=18"
|
| 1721 |
}
|
| 1722 |
},
|
| 1723 |
+
"node_modules/@socket.io/component-emitter": {
|
| 1724 |
+
"version": "3.1.2",
|
| 1725 |
+
"resolved": "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
| 1726 |
+
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
| 1727 |
+
"license": "MIT"
|
| 1728 |
+
},
|
| 1729 |
"node_modules/@tokenizer/inflate": {
|
| 1730 |
"version": "0.4.1",
|
| 1731 |
"resolved": "https://registry.npmmirror.com/@tokenizer/inflate/-/inflate-0.4.1.tgz",
|
|
|
|
| 3097 |
"once": "^1.4.0"
|
| 3098 |
}
|
| 3099 |
},
|
| 3100 |
+
"node_modules/engine.io-client": {
|
| 3101 |
+
"version": "6.6.6",
|
| 3102 |
+
"resolved": "https://registry.npmmirror.com/engine.io-client/-/engine.io-client-6.6.6.tgz",
|
| 3103 |
+
"integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==",
|
| 3104 |
+
"license": "MIT",
|
| 3105 |
+
"dependencies": {
|
| 3106 |
+
"@socket.io/component-emitter": "~3.1.0",
|
| 3107 |
+
"debug": "~4.4.1",
|
| 3108 |
+
"engine.io-parser": "~5.2.1",
|
| 3109 |
+
"ws": "~8.21.0",
|
| 3110 |
+
"xmlhttprequest-ssl": "~2.1.1"
|
| 3111 |
+
}
|
| 3112 |
+
},
|
| 3113 |
+
"node_modules/engine.io-client/node_modules/debug": {
|
| 3114 |
+
"version": "4.4.3",
|
| 3115 |
+
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
| 3116 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 3117 |
+
"license": "MIT",
|
| 3118 |
+
"dependencies": {
|
| 3119 |
+
"ms": "^2.1.3"
|
| 3120 |
+
},
|
| 3121 |
+
"engines": {
|
| 3122 |
+
"node": ">=6.0"
|
| 3123 |
+
},
|
| 3124 |
+
"peerDependenciesMeta": {
|
| 3125 |
+
"supports-color": {
|
| 3126 |
+
"optional": true
|
| 3127 |
+
}
|
| 3128 |
+
}
|
| 3129 |
+
},
|
| 3130 |
+
"node_modules/engine.io-client/node_modules/ms": {
|
| 3131 |
+
"version": "2.1.3",
|
| 3132 |
+
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
| 3133 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 3134 |
+
"license": "MIT"
|
| 3135 |
+
},
|
| 3136 |
+
"node_modules/engine.io-parser": {
|
| 3137 |
+
"version": "5.2.3",
|
| 3138 |
+
"resolved": "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
| 3139 |
+
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
| 3140 |
+
"license": "MIT",
|
| 3141 |
+
"engines": {
|
| 3142 |
+
"node": ">=10.0.0"
|
| 3143 |
+
}
|
| 3144 |
+
},
|
| 3145 |
"node_modules/es-define-property": {
|
| 3146 |
"version": "1.0.1",
|
| 3147 |
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
|
|
|
| 6573 |
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
|
| 6574 |
"license": "MIT"
|
| 6575 |
},
|
| 6576 |
+
"node_modules/socket.io-client": {
|
| 6577 |
+
"version": "4.8.3",
|
| 6578 |
+
"resolved": "https://registry.npmmirror.com/socket.io-client/-/socket.io-client-4.8.3.tgz",
|
| 6579 |
+
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
|
| 6580 |
+
"license": "MIT",
|
| 6581 |
+
"dependencies": {
|
| 6582 |
+
"@socket.io/component-emitter": "~3.1.0",
|
| 6583 |
+
"debug": "~4.4.1",
|
| 6584 |
+
"engine.io-client": "~6.6.1",
|
| 6585 |
+
"socket.io-parser": "~4.2.4"
|
| 6586 |
+
},
|
| 6587 |
+
"engines": {
|
| 6588 |
+
"node": ">=10.0.0"
|
| 6589 |
+
}
|
| 6590 |
+
},
|
| 6591 |
+
"node_modules/socket.io-client/node_modules/debug": {
|
| 6592 |
+
"version": "4.4.3",
|
| 6593 |
+
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
| 6594 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 6595 |
+
"license": "MIT",
|
| 6596 |
+
"dependencies": {
|
| 6597 |
+
"ms": "^2.1.3"
|
| 6598 |
+
},
|
| 6599 |
+
"engines": {
|
| 6600 |
+
"node": ">=6.0"
|
| 6601 |
+
},
|
| 6602 |
+
"peerDependenciesMeta": {
|
| 6603 |
+
"supports-color": {
|
| 6604 |
+
"optional": true
|
| 6605 |
+
}
|
| 6606 |
+
}
|
| 6607 |
+
},
|
| 6608 |
+
"node_modules/socket.io-client/node_modules/ms": {
|
| 6609 |
+
"version": "2.1.3",
|
| 6610 |
+
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
| 6611 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 6612 |
+
"license": "MIT"
|
| 6613 |
+
},
|
| 6614 |
+
"node_modules/socket.io-parser": {
|
| 6615 |
+
"version": "4.2.7",
|
| 6616 |
+
"resolved": "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.7.tgz",
|
| 6617 |
+
"integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==",
|
| 6618 |
+
"license": "MIT",
|
| 6619 |
+
"dependencies": {
|
| 6620 |
+
"@socket.io/component-emitter": "~3.1.0",
|
| 6621 |
+
"debug": "~4.4.1"
|
| 6622 |
+
},
|
| 6623 |
+
"engines": {
|
| 6624 |
+
"node": ">=10.0.0"
|
| 6625 |
+
}
|
| 6626 |
+
},
|
| 6627 |
+
"node_modules/socket.io-parser/node_modules/debug": {
|
| 6628 |
+
"version": "4.4.3",
|
| 6629 |
+
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
|
| 6630 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 6631 |
+
"license": "MIT",
|
| 6632 |
+
"dependencies": {
|
| 6633 |
+
"ms": "^2.1.3"
|
| 6634 |
+
},
|
| 6635 |
+
"engines": {
|
| 6636 |
+
"node": ">=6.0"
|
| 6637 |
+
},
|
| 6638 |
+
"peerDependenciesMeta": {
|
| 6639 |
+
"supports-color": {
|
| 6640 |
+
"optional": true
|
| 6641 |
+
}
|
| 6642 |
+
}
|
| 6643 |
+
},
|
| 6644 |
+
"node_modules/socket.io-parser/node_modules/ms": {
|
| 6645 |
+
"version": "2.1.3",
|
| 6646 |
+
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
|
| 6647 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 6648 |
+
"license": "MIT"
|
| 6649 |
+
},
|
| 6650 |
"node_modules/sparse-bitfield": {
|
| 6651 |
"version": "3.0.3",
|
| 6652 |
"resolved": "https://registry.npmmirror.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
|
|
|
| 7557 |
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
| 7558 |
"license": "ISC"
|
| 7559 |
},
|
| 7560 |
+
"node_modules/ws": {
|
| 7561 |
+
"version": "8.21.1",
|
| 7562 |
+
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.1.tgz",
|
| 7563 |
+
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
|
| 7564 |
+
"license": "MIT",
|
| 7565 |
+
"engines": {
|
| 7566 |
+
"node": ">=10.0.0"
|
| 7567 |
+
},
|
| 7568 |
+
"peerDependencies": {
|
| 7569 |
+
"bufferutil": "^4.0.1",
|
| 7570 |
+
"utf-8-validate": ">=5.0.2"
|
| 7571 |
+
},
|
| 7572 |
+
"peerDependenciesMeta": {
|
| 7573 |
+
"bufferutil": {
|
| 7574 |
+
"optional": true
|
| 7575 |
+
},
|
| 7576 |
+
"utf-8-validate": {
|
| 7577 |
+
"optional": true
|
| 7578 |
+
}
|
| 7579 |
+
}
|
| 7580 |
+
},
|
| 7581 |
"node_modules/xml": {
|
| 7582 |
"version": "1.0.1",
|
| 7583 |
"resolved": "https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz",
|
|
|
|
| 7624 |
"node": ">=4.0"
|
| 7625 |
}
|
| 7626 |
},
|
| 7627 |
+
"node_modules/xmlhttprequest-ssl": {
|
| 7628 |
+
"version": "2.1.2",
|
| 7629 |
+
"resolved": "https://registry.npmmirror.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
| 7630 |
+
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
| 7631 |
+
"engines": {
|
| 7632 |
+
"node": ">=0.4.0"
|
| 7633 |
+
}
|
| 7634 |
+
},
|
| 7635 |
"node_modules/xtend": {
|
| 7636 |
"version": "4.0.2",
|
| 7637 |
"resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
|
package.json
CHANGED
|
@@ -75,6 +75,7 @@
|
|
| 75 |
"passport-oauth2": "^1.8.0",
|
| 76 |
"pdf-parse": "^1.1.4",
|
| 77 |
"sharp": "^0.32.6",
|
|
|
|
| 78 |
"stripe": "^22.2.2",
|
| 79 |
"swagger-jsdoc": "^6.2.8",
|
| 80 |
"swagger-ui-express": "^5.0.1",
|
|
|
|
| 75 |
"passport-oauth2": "^1.8.0",
|
| 76 |
"pdf-parse": "^1.1.4",
|
| 77 |
"sharp": "^0.32.6",
|
| 78 |
+
"socket.io-client": "^4.8.3",
|
| 79 |
"stripe": "^22.2.2",
|
| 80 |
"swagger-jsdoc": "^6.2.8",
|
| 81 |
"swagger-ui-express": "^5.0.1",
|
server.js
CHANGED
|
@@ -76,6 +76,8 @@ const helmet = require('helmet');
|
|
| 76 |
const app = express();
|
| 77 |
app.set('trust proxy', true);
|
| 78 |
|
|
|
|
|
|
|
| 79 |
|
| 80 |
|
| 81 |
app.use(cors({
|
|
@@ -377,6 +379,8 @@ const excludedPaths = [
|
|
| 377 |
'/api/subscription/renew',
|
| 378 |
'/api/subscription/cancel',
|
| 379 |
'/api/users/:userId/badges',
|
|
|
|
|
|
|
| 380 |
'/api/users/:userId',
|
| 381 |
'/api/admin/stores/:userId/toggle',
|
| 382 |
'/api/admin/stores/:userId',
|
|
@@ -1109,17 +1113,18 @@ const handleUploadError = (err, req, res, next) => {
|
|
| 1109 |
// ============================================
|
| 1110 |
const messageUpload = multer({
|
| 1111 |
storage: multer.memoryStorage(),
|
| 1112 |
-
limits: { fileSize: 10 * 1024 * 1024 },
|
| 1113 |
fileFilter: (req, file, cb) => {
|
| 1114 |
const allowedTypes = [
|
| 1115 |
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
|
|
|
| 1116 |
'application/pdf',
|
| 1117 |
'audio/mpeg', 'audio/webm', 'audio/mp4', 'audio/ogg'
|
| 1118 |
];
|
| 1119 |
if (allowedTypes.includes(file.mimetype)) {
|
| 1120 |
cb(null, true);
|
| 1121 |
} else {
|
| 1122 |
-
cb(new Error('File type not allowed. Allowed: images, PDF, audio
|
| 1123 |
}
|
| 1124 |
}
|
| 1125 |
});
|
|
@@ -4348,6 +4353,12 @@ const userSchema = new mongoose.Schema({
|
|
| 4348 |
interaction: {
|
| 4349 |
bronze: { type: Boolean, default: false }, // 10 ردود
|
| 4350 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4351 |
unlockedAt: { type: Date, default: null },
|
| 4352 |
updatedAt: { type: Date, default: Date.now }
|
| 4353 |
},
|
|
@@ -4356,6 +4367,7 @@ const userSchema = new mongoose.Schema({
|
|
| 4356 |
likes: { bronze: false, silver: false, gold: false, red: false },
|
| 4357 |
interaction: { bronze: false },
|
| 4358 |
unlockedAt: null,
|
|
|
|
| 4359 |
updatedAt: Date.now
|
| 4360 |
}
|
| 4361 |
},
|
|
@@ -10284,6 +10296,136 @@ app.get('/api/profile/:nickname', async (req, res) => {
|
|
| 10284 |
}
|
| 10285 |
});
|
| 10286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10287 |
// ============================================
|
| 10288 |
// GET /api/users/:userId/badges - جلب البرونزيات المفتوحة فقط
|
| 10289 |
// ============================================
|
|
@@ -10291,21 +10433,22 @@ app.get('/api/users/:userId/badges', async (req, res) => {
|
|
| 10291 |
try {
|
| 10292 |
const { userId } = req.params;
|
| 10293 |
|
| 10294 |
-
// ✅ جلب جميع البيانات بشكل متوازي
|
| 10295 |
-
const [badges, viewCount, likeCount, replyCount] = await Promise.all([
|
| 10296 |
getUserBadges(userId),
|
| 10297 |
getUniqueViewsCount(userId),
|
| 10298 |
getTotalPostLikes(userId),
|
| 10299 |
-
getTotalUserReplies(userId)
|
|
|
|
| 10300 |
]);
|
| 10301 |
|
| 10302 |
-
// ✅ حساب التالي
|
| 10303 |
let nextBadge = null;
|
| 10304 |
let nextThreshold = null;
|
| 10305 |
let progress = 100;
|
| 10306 |
let nextBadgeType = null;
|
| 10307 |
|
| 10308 |
-
// ✅
|
| 10309 |
const viewsBadges = badges.views || { unlocked: [], count: 0 };
|
| 10310 |
const hasSilver = viewsBadges.unlocked.some(b => b.key === 'silver');
|
| 10311 |
const hasGold = viewsBadges.unlocked.some(b => b.key === 'gold');
|
|
@@ -10328,7 +10471,7 @@ app.get('/api/users/:userId/badges', async (req, res) => {
|
|
| 10328 |
nextBadgeType = 'views';
|
| 10329 |
}
|
| 10330 |
|
| 10331 |
-
// ✅
|
| 10332 |
const likesBadges = badges.likes || { unlocked: [], count: 0 };
|
| 10333 |
const hasLikesBronze = likesBadges.unlocked.some(b => b.key === 'bronze');
|
| 10334 |
const hasLikesRed = likesBadges.unlocked.some(b => b.key === 'red');
|
|
@@ -10359,7 +10502,7 @@ app.get('/api/users/:userId/badges', async (req, res) => {
|
|
| 10359 |
}
|
| 10360 |
}
|
| 10361 |
|
| 10362 |
-
// ✅
|
| 10363 |
const interactionBadges = badges.interaction || { unlocked: [], count: 0 };
|
| 10364 |
const hasInteractionBronze = interactionBadges.unlocked.some(b => b.key === 'bronze');
|
| 10365 |
|
|
@@ -10372,7 +10515,37 @@ app.get('/api/users/:userId/badges', async (req, res) => {
|
|
| 10372 |
}
|
| 10373 |
}
|
| 10374 |
|
| 10375 |
-
// ✅
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10376 |
if (!nextBadge) {
|
| 10377 |
nextBadge = 'All badges collected! 🎉';
|
| 10378 |
nextThreshold = null;
|
|
@@ -10380,54 +10553,377 @@ app.get('/api/users/:userId/badges', async (req, res) => {
|
|
| 10380 |
nextBadgeType = 'complete';
|
| 10381 |
}
|
| 10382 |
|
| 10383 |
-
// ✅ حساب نسبة التقدم الإجمالية
|
| 10384 |
const totalBadges = badges.totalCount || 0;
|
| 10385 |
-
const totalPossible = 3 + 4 + 1; // views(3) + likes(4) + interaction(1)
|
| 10386 |
const overallProgress = Math.min((totalBadges / totalPossible) * 100, 100);
|
| 10387 |
|
|
|
|
| 10388 |
res.json({
|
| 10389 |
-
|
| 10390 |
-
|
| 10391 |
-
|
| 10392 |
-
|
| 10393 |
-
|
| 10394 |
-
|
| 10395 |
-
|
| 10396 |
-
|
| 10397 |
-
|
| 10398 |
-
|
| 10399 |
-
|
| 10400 |
-
|
| 10401 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10402 |
},
|
| 10403 |
-
|
| 10404 |
-
|
| 10405 |
-
|
|
|
|
|
|
|
| 10406 |
},
|
| 10407 |
-
|
| 10408 |
-
|
| 10409 |
-
|
| 10410 |
-
|
| 10411 |
-
|
| 10412 |
-
|
| 10413 |
-
|
| 10414 |
-
}
|
| 10415 |
-
nextBadge,
|
| 10416 |
-
nextThreshold,
|
| 10417 |
-
nextBadgeType,
|
| 10418 |
-
progress: Math.min(Math.round(progress), 100),
|
| 10419 |
-
overallProgress: Math.min(Math.round(overallProgress), 100),
|
| 10420 |
-
totalBadges,
|
| 10421 |
-
totalPossible
|
| 10422 |
});
|
| 10423 |
|
| 10424 |
} catch (error) {
|
| 10425 |
console.error('Error getting badges:', error);
|
| 10426 |
-
res.status(500).json({
|
|
|
|
|
|
|
|
|
|
| 10427 |
}
|
| 10428 |
});
|
| 10429 |
|
| 10430 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10431 |
|
| 10432 |
// ============================================
|
| 10433 |
// GET /api/users/:userId - جلب بيانات مستخدم معين للوسام
|
|
@@ -18515,9 +19011,12 @@ app.post('/api/conversations/:conversationId/messages', authenticateToken, messa
|
|
| 18515 |
uploadStream.end(file.buffer);
|
| 18516 |
});
|
| 18517 |
|
| 18518 |
-
|
| 18519 |
if (file.mimetype.startsWith('image/')) attachmentType = 'image';
|
|
|
|
| 18520 |
else if (file.mimetype.startsWith('audio/')) attachmentType = 'audio';
|
|
|
|
|
|
|
| 18521 |
|
| 18522 |
attachments.push({
|
| 18523 |
type: attachmentType,
|
|
@@ -18580,6 +19079,27 @@ app.post('/api/conversations/:conversationId/messages', authenticateToken, messa
|
|
| 18580 |
|
| 18581 |
const populatedMessage = await message.populate('senderId', 'username profile.nickname profile.avatar');
|
| 18582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18583 |
res.status(201).json({ success: true, data: populatedMessage });
|
| 18584 |
|
| 18585 |
} catch (error) {
|
|
@@ -36050,6 +36570,7 @@ function generateStaticSitemap() {
|
|
| 36050 |
{ loc: '/feed.html', changefreq: 'daily', priority: 0.9 },
|
| 36051 |
{ loc: '/videos.html', changefreq: 'daily', priority: 0.9 },
|
| 36052 |
{ loc: '/video.html', changefreq: 'daily', priority: 0.9 },
|
|
|
|
| 36053 |
{ loc: '/template-store.html', changefreq: 'daily', priority: 0.9 },
|
| 36054 |
{ loc: '/explore.html', changefreq: 'daily', priority: 0.9 },
|
| 36055 |
{ loc: '/profile.html', changefreq: 'daily', priority: 0.9 },
|
|
|
|
| 76 |
const app = express();
|
| 77 |
app.set('trust proxy', true);
|
| 78 |
|
| 79 |
+
const getSocketClient = require('./utils/socket-client');
|
| 80 |
+
|
| 81 |
|
| 82 |
|
| 83 |
app.use(cors({
|
|
|
|
| 379 |
'/api/subscription/renew',
|
| 380 |
'/api/subscription/cancel',
|
| 381 |
'/api/users/:userId/badges',
|
| 382 |
+
'/api/profile/:userId/view',
|
| 383 |
+
'/api/achievements/:slug',
|
| 384 |
'/api/users/:userId',
|
| 385 |
'/api/admin/stores/:userId/toggle',
|
| 386 |
'/api/admin/stores/:userId',
|
|
|
|
| 1113 |
// ============================================
|
| 1114 |
const messageUpload = multer({
|
| 1115 |
storage: multer.memoryStorage(),
|
| 1116 |
+
limits: { fileSize: 10 * 1024 * 1024 },
|
| 1117 |
fileFilter: (req, file, cb) => {
|
| 1118 |
const allowedTypes = [
|
| 1119 |
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
| 1120 |
+
'video/mp4', 'video/webm', 'video/ogg',
|
| 1121 |
'application/pdf',
|
| 1122 |
'audio/mpeg', 'audio/webm', 'audio/mp4', 'audio/ogg'
|
| 1123 |
];
|
| 1124 |
if (allowedTypes.includes(file.mimetype)) {
|
| 1125 |
cb(null, true);
|
| 1126 |
} else {
|
| 1127 |
+
cb(new Error('File type not allowed. Allowed: images, videos, PDF, audio'), false);
|
| 1128 |
}
|
| 1129 |
}
|
| 1130 |
});
|
|
|
|
| 4353 |
interaction: {
|
| 4354 |
bronze: { type: Boolean, default: false }, // 10 ردود
|
| 4355 |
},
|
| 4356 |
+
ratings: {
|
| 4357 |
+
bronze: { type: Boolean, default: false },
|
| 4358 |
+
silver: { type: Boolean, default: false },
|
| 4359 |
+
gold: { type: Boolean, default: false },
|
| 4360 |
+
platinum: { type: Boolean, default: false }
|
| 4361 |
+
},
|
| 4362 |
unlockedAt: { type: Date, default: null },
|
| 4363 |
updatedAt: { type: Date, default: Date.now }
|
| 4364 |
},
|
|
|
|
| 4367 |
likes: { bronze: false, silver: false, gold: false, red: false },
|
| 4368 |
interaction: { bronze: false },
|
| 4369 |
unlockedAt: null,
|
| 4370 |
+
ratings: { bronze: false, silver: false, gold: false, platinum: false },
|
| 4371 |
updatedAt: Date.now
|
| 4372 |
}
|
| 4373 |
},
|
|
|
|
| 10296 |
}
|
| 10297 |
});
|
| 10298 |
|
| 10299 |
+
|
| 10300 |
+
// ============================================
|
| 10301 |
+
// POST /api/profile/:userId/view - تتبع مشاهدة البروفايل
|
| 10302 |
+
// ============================================
|
| 10303 |
+
app.post('/api/profile/:userId/view', async (req, res) => {
|
| 10304 |
+
try {
|
| 10305 |
+
const { userId: targetUserId } = req.params;
|
| 10306 |
+
|
| 10307 |
+
if (!targetUserId) {
|
| 10308 |
+
return res.status(400).json({
|
| 10309 |
+
success: false,
|
| 10310 |
+
error: 'targetUserId is required'
|
| 10311 |
+
});
|
| 10312 |
+
}
|
| 10313 |
+
|
| 10314 |
+
// ✅ الحصول على viewerId من التوكن (لو موجود)
|
| 10315 |
+
const viewerId = req.user?.userId || null;
|
| 10316 |
+
const viewerIp = req.ip || req.connection?.remoteAddress || 'unknown';
|
| 10317 |
+
|
| 10318 |
+
// ✅ لا تسجل مشاهدة لنفس المستخدم
|
| 10319 |
+
if (viewerId === targetUserId) {
|
| 10320 |
+
return res.json({
|
| 10321 |
+
success: true,
|
| 10322 |
+
message: 'Self view not tracked',
|
| 10323 |
+
data: { selfView: true }
|
| 10324 |
+
});
|
| 10325 |
+
}
|
| 10326 |
+
|
| 10327 |
+
// ============================
|
| 10328 |
+
// 📊 1. تسجيل في ProfileAnalytics
|
| 10329 |
+
// ============================
|
| 10330 |
+
let analytics = await ProfileAnalytics.findOne({ userId: targetUserId });
|
| 10331 |
+
|
| 10332 |
+
if (!analytics) {
|
| 10333 |
+
analytics = new ProfileAnalytics({ userId: targetUserId });
|
| 10334 |
+
}
|
| 10335 |
+
|
| 10336 |
+
analytics.profileViews = analytics.profileViews || [];
|
| 10337 |
+
analytics.profileViews.push({
|
| 10338 |
+
viewerId: viewerId,
|
| 10339 |
+
viewerIp: viewerIp,
|
| 10340 |
+
viewedAt: new Date()
|
| 10341 |
+
});
|
| 10342 |
+
|
| 10343 |
+
// ✅ الاحتفاظ بآخر 100 مشاهدة فقط
|
| 10344 |
+
if (analytics.profileViews.length > 100) {
|
| 10345 |
+
analytics.profileViews = analytics.profileViews.slice(-100);
|
| 10346 |
+
}
|
| 10347 |
+
|
| 10348 |
+
analytics.lastUpdated = new Date();
|
| 10349 |
+
await analytics.save();
|
| 10350 |
+
|
| 10351 |
+
// ============================
|
| 10352 |
+
// 👥 2. تسجيل في AlsoViewed
|
| 10353 |
+
// ============================
|
| 10354 |
+
if (viewerId) {
|
| 10355 |
+
// ✅ مستخدم مسجل
|
| 10356 |
+
await AlsoViewed.findOneAndUpdate(
|
| 10357 |
+
{ targetUserId, viewerId: viewerId },
|
| 10358 |
+
{
|
| 10359 |
+
viewedAt: new Date(),
|
| 10360 |
+
viewerIp: viewerIp
|
| 10361 |
+
},
|
| 10362 |
+
{ upsert: true }
|
| 10363 |
+
);
|
| 10364 |
+
} else {
|
| 10365 |
+
// ✅ زائر (غير مسجل) - نستخدم IP
|
| 10366 |
+
const existingView = await AlsoViewed.findOne({
|
| 10367 |
+
targetUserId,
|
| 10368 |
+
viewerIp: viewerIp,
|
| 10369 |
+
viewedAt: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
|
| 10370 |
+
});
|
| 10371 |
+
|
| 10372 |
+
if (!existingView) {
|
| 10373 |
+
await AlsoViewed.create({
|
| 10374 |
+
targetUserId,
|
| 10375 |
+
viewerId: null,
|
| 10376 |
+
viewerIp: viewerIp,
|
| 10377 |
+
viewedAt: new Date()
|
| 10378 |
+
});
|
| 10379 |
+
}
|
| 10380 |
+
}
|
| 10381 |
+
|
| 10382 |
+
// ============================
|
| 10383 |
+
// 🏆 3. تحديث البرونزيات
|
| 10384 |
+
// ============================
|
| 10385 |
+
const badgeResult = await updateUserBadges(targetUserId);
|
| 10386 |
+
|
| 10387 |
+
// ============================
|
| 10388 |
+
// 📢 4. بث للمستخدم المستهدف (لو فتح برونزية) - عبر Socket
|
| 10389 |
+
// ============================
|
| 10390 |
+
if (badgeResult?.newBadgeUnlocked && badgeResult?.unlockedBadges?.length > 0) {
|
| 10391 |
+
// ✅ استخدم io.to (نفس الكود القديم)
|
| 10392 |
+
io.to(`user_${targetUserId}`).emit('badge_unlocked', {
|
| 10393 |
+
userId: targetUserId,
|
| 10394 |
+
badges: badgeResult.unlockedBadges,
|
| 10395 |
+
badgeIcons: badgeResult.unlockedBadgeIcons || [],
|
| 10396 |
+
viewCount: badgeResult.viewCount,
|
| 10397 |
+
likeCount: badgeResult.likeCount,
|
| 10398 |
+
replyCount: badgeResult.replyCount,
|
| 10399 |
+
ratingCount: badgeResult.ratingCount,
|
| 10400 |
+
totalBadges: badgeResult.totalBadgeCount
|
| 10401 |
+
});
|
| 10402 |
+
}
|
| 10403 |
+
|
| 10404 |
+
res.json({
|
| 10405 |
+
success: true,
|
| 10406 |
+
message: 'View tracked successfully',
|
| 10407 |
+
data: {
|
| 10408 |
+
viewCount: badgeResult?.viewCount || 0,
|
| 10409 |
+
likeCount: badgeResult?.likeCount || 0,
|
| 10410 |
+
replyCount: badgeResult?.replyCount || 0,
|
| 10411 |
+
ratingCount: badgeResult?.ratingCount || 0,
|
| 10412 |
+
totalBadges: badgeResult?.totalBadgeCount || 0,
|
| 10413 |
+
newBadgeUnlocked: badgeResult?.newBadgeUnlocked || false,
|
| 10414 |
+
unlockedBadges: badgeResult?.unlockedBadges || []
|
| 10415 |
+
}
|
| 10416 |
+
});
|
| 10417 |
+
|
| 10418 |
+
} catch (error) {
|
| 10419 |
+
console.error('❌ Track profile view error:', error);
|
| 10420 |
+
// ✅ لا نرسل خطأ للمستخدم حتى لا يسبب مشاكل
|
| 10421 |
+
res.json({
|
| 10422 |
+
success: true,
|
| 10423 |
+
message: 'View tracked',
|
| 10424 |
+
data: { error: error.message }
|
| 10425 |
+
});
|
| 10426 |
+
}
|
| 10427 |
+
});
|
| 10428 |
+
|
| 10429 |
// ============================================
|
| 10430 |
// GET /api/users/:userId/badges - جلب البرونزيات المفتوحة فقط
|
| 10431 |
// ============================================
|
|
|
|
| 10433 |
try {
|
| 10434 |
const { userId } = req.params;
|
| 10435 |
|
| 10436 |
+
// ✅ جلب جميع البيانات بشكل متوازي (نفس الـ Socket)
|
| 10437 |
+
const [badges, viewCount, likeCount, replyCount, ratingCount] = await Promise.all([
|
| 10438 |
getUserBadges(userId),
|
| 10439 |
getUniqueViewsCount(userId),
|
| 10440 |
getTotalPostLikes(userId),
|
| 10441 |
+
getTotalUserReplies(userId),
|
| 10442 |
+
getTotalUserRatings(userId) // ✅ أضف ratingCount
|
| 10443 |
]);
|
| 10444 |
|
| 10445 |
+
// ✅ حساب التالي - نفس منطق الـ Socket بالضبط
|
| 10446 |
let nextBadge = null;
|
| 10447 |
let nextThreshold = null;
|
| 10448 |
let progress = 100;
|
| 10449 |
let nextBadgeType = null;
|
| 10450 |
|
| 10451 |
+
// ✅ Views Badges
|
| 10452 |
const viewsBadges = badges.views || { unlocked: [], count: 0 };
|
| 10453 |
const hasSilver = viewsBadges.unlocked.some(b => b.key === 'silver');
|
| 10454 |
const hasGold = viewsBadges.unlocked.some(b => b.key === 'gold');
|
|
|
|
| 10471 |
nextBadgeType = 'views';
|
| 10472 |
}
|
| 10473 |
|
| 10474 |
+
// ✅ Likes Badges
|
| 10475 |
const likesBadges = badges.likes || { unlocked: [], count: 0 };
|
| 10476 |
const hasLikesBronze = likesBadges.unlocked.some(b => b.key === 'bronze');
|
| 10477 |
const hasLikesRed = likesBadges.unlocked.some(b => b.key === 'red');
|
|
|
|
| 10502 |
}
|
| 10503 |
}
|
| 10504 |
|
| 10505 |
+
// ✅ Interaction Badges
|
| 10506 |
const interactionBadges = badges.interaction || { unlocked: [], count: 0 };
|
| 10507 |
const hasInteractionBronze = interactionBadges.unlocked.some(b => b.key === 'bronze');
|
| 10508 |
|
|
|
|
| 10515 |
}
|
| 10516 |
}
|
| 10517 |
|
| 10518 |
+
// ✅ Ratings Badges
|
| 10519 |
+
const ratingsBadges = badges.ratings || { unlocked: [], count: 0 };
|
| 10520 |
+
const hasRatingsBronze = ratingsBadges.unlocked.some(b => b.key === 'bronze');
|
| 10521 |
+
const hasRatingsSilver = ratingsBadges.unlocked.some(b => b.key === 'silver');
|
| 10522 |
+
const hasRatingsGold = ratingsBadges.unlocked.some(b => b.key === 'gold');
|
| 10523 |
+
const hasRatingsPlatinum = ratingsBadges.unlocked.some(b => b.key === 'platinum');
|
| 10524 |
+
|
| 10525 |
+
if (!nextBadge) {
|
| 10526 |
+
if (!hasRatingsBronze && ratingCount < RATING_BADGE_THRESHOLDS.BRONZE) {
|
| 10527 |
+
nextBadge = 'Ratings Bronze';
|
| 10528 |
+
nextThreshold = RATING_BADGE_THRESHOLDS.BRONZE;
|
| 10529 |
+
progress = (ratingCount / RATING_BADGE_THRESHOLDS.BRONZE) * 100;
|
| 10530 |
+
nextBadgeType = 'ratings';
|
| 10531 |
+
} else if (!hasRatingsSilver && ratingCount < RATING_BADGE_THRESHOLDS.SILVER) {
|
| 10532 |
+
nextBadge = 'Ratings Silver';
|
| 10533 |
+
nextThreshold = RATING_BADGE_THRESHOLDS.SILVER;
|
| 10534 |
+
progress = (ratingCount / RATING_BADGE_THRESHOLDS.SILVER) * 100;
|
| 10535 |
+
nextBadgeType = 'ratings';
|
| 10536 |
+
} else if (!hasRatingsGold && ratingCount < RATING_BADGE_THRESHOLDS.GOLD) {
|
| 10537 |
+
nextBadge = 'Ratings Gold';
|
| 10538 |
+
nextThreshold = RATING_BADGE_THRESHOLDS.GOLD;
|
| 10539 |
+
progress = (ratingCount / RATING_BADGE_THRESHOLDS.GOLD) * 100;
|
| 10540 |
+
nextBadgeType = 'ratings';
|
| 10541 |
+
} else if (!hasRatingsPlatinum && ratingCount < RATING_BADGE_THRESHOLDS.PLATINUM) {
|
| 10542 |
+
nextBadge = 'Ratings Platinum';
|
| 10543 |
+
nextThreshold = RATING_BADGE_THRESHOLDS.PLATINUM;
|
| 10544 |
+
progress = (ratingCount / RATING_BADGE_THRESHOLDS.PLATINUM) * 100;
|
| 10545 |
+
nextBadgeType = 'ratings';
|
| 10546 |
+
}
|
| 10547 |
+
}
|
| 10548 |
+
|
| 10549 |
if (!nextBadge) {
|
| 10550 |
nextBadge = 'All badges collected! 🎉';
|
| 10551 |
nextThreshold = null;
|
|
|
|
| 10553 |
nextBadgeType = 'complete';
|
| 10554 |
}
|
| 10555 |
|
|
|
|
| 10556 |
const totalBadges = badges.totalCount || 0;
|
| 10557 |
+
const totalPossible = 3 + 4 + 1 + 4; // views(3) + likes(4) + interaction(1) + ratings(4)
|
| 10558 |
const overallProgress = Math.min((totalBadges / totalPossible) * 100, 100);
|
| 10559 |
|
| 10560 |
+
// ✅ نفس بنية البيانات تماماً زي الـ Socket
|
| 10561 |
res.json({
|
| 10562 |
+
success: true,
|
| 10563 |
+
data: {
|
| 10564 |
+
userId,
|
| 10565 |
+
viewCount,
|
| 10566 |
+
likeCount,
|
| 10567 |
+
replyCount,
|
| 10568 |
+
ratingCount,
|
| 10569 |
+
badges: {
|
| 10570 |
+
views: {
|
| 10571 |
+
unlocked: badges.views?.unlocked || [],
|
| 10572 |
+
count: badges.views?.count || 0
|
| 10573 |
+
},
|
| 10574 |
+
likes: {
|
| 10575 |
+
unlocked: badges.likes?.unlocked || [],
|
| 10576 |
+
count: badges.likes?.count || 0
|
| 10577 |
+
},
|
| 10578 |
+
interaction: {
|
| 10579 |
+
unlocked: badges.interaction?.unlocked || [],
|
| 10580 |
+
count: badges.interaction?.count || 0
|
| 10581 |
+
},
|
| 10582 |
+
ratings: {
|
| 10583 |
+
unlocked: badges.ratings?.unlocked || [],
|
| 10584 |
+
count: badges.ratings?.count || 0
|
| 10585 |
+
},
|
| 10586 |
+
unlocked: badges.unlocked || [],
|
| 10587 |
+
totalCount: totalBadges
|
| 10588 |
},
|
| 10589 |
+
thresholds: {
|
| 10590 |
+
views: VIEW_BADGE_THRESHOLDS,
|
| 10591 |
+
likes: LIKE_BADGE_THRESHOLDS,
|
| 10592 |
+
interaction: INTERACTION_BADGE_THRESHOLDS,
|
| 10593 |
+
ratings: RATING_BADGE_THRESHOLDS
|
| 10594 |
},
|
| 10595 |
+
nextBadge,
|
| 10596 |
+
nextThreshold,
|
| 10597 |
+
nextBadgeType,
|
| 10598 |
+
progress: Math.min(Math.round(progress), 100),
|
| 10599 |
+
overallProgress: Math.min(Math.round(overallProgress), 100),
|
| 10600 |
+
totalBadges,
|
| 10601 |
+
totalPossible
|
| 10602 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10603 |
});
|
| 10604 |
|
| 10605 |
} catch (error) {
|
| 10606 |
console.error('Error getting badges:', error);
|
| 10607 |
+
res.status(500).json({
|
| 10608 |
+
success: false,
|
| 10609 |
+
error: 'Failed to get badges'
|
| 10610 |
+
});
|
| 10611 |
}
|
| 10612 |
});
|
| 10613 |
|
| 10614 |
|
| 10615 |
+
// ============================================
|
| 10616 |
+
// GET /api/achievements/:slug - جلب تفاصيل برونزية معينة
|
| 10617 |
+
// ============================================
|
| 10618 |
+
app.get('/api/achievements/:slug', async (req, res) => {
|
| 10619 |
+
try {
|
| 10620 |
+
const { slug } = req.params;
|
| 10621 |
+
const { userId } = req.query; // ✅ userId من query string
|
| 10622 |
+
|
| 10623 |
+
// ✅ تعريف كل البرونزيات مع بياناتها الكاملة
|
| 10624 |
+
const achievements = {
|
| 10625 |
+
'views-silver': {
|
| 10626 |
+
name: 'Views Silver',
|
| 10627 |
+
slug: 'views-silver',
|
| 10628 |
+
icon: '/assets/img/fire.svg',
|
| 10629 |
+
type: 'views',
|
| 10630 |
+
description: 'Achieved 20 unique profile views. Your profile is getting noticed!',
|
| 10631 |
+
rarity: 'uncommon',
|
| 10632 |
+
rarityScore: 15,
|
| 10633 |
+
threshold: 20,
|
| 10634 |
+
requirements: [
|
| 10635 |
+
{ text: 'Get 20 unique profile views', max: 20 }
|
| 10636 |
+
]
|
| 10637 |
+
},
|
| 10638 |
+
'views-gold': {
|
| 10639 |
+
name: 'Views Gold',
|
| 10640 |
+
slug: 'views-gold',
|
| 10641 |
+
icon: '/assets/img/firegolden.svg',
|
| 10642 |
+
type: 'views',
|
| 10643 |
+
description: 'Achieved 100 unique profile views. You\'re building a strong presence!',
|
| 10644 |
+
rarity: 'rare',
|
| 10645 |
+
rarityScore: 30,
|
| 10646 |
+
threshold: 100,
|
| 10647 |
+
requirements: [
|
| 10648 |
+
{ text: 'Get 100 unique profile views', max: 100 }
|
| 10649 |
+
]
|
| 10650 |
+
},
|
| 10651 |
+
'views-red': {
|
| 10652 |
+
name: 'Views Red',
|
| 10653 |
+
slug: 'views-red',
|
| 10654 |
+
icon: '/assets/img/firered.svg',
|
| 10655 |
+
type: 'views',
|
| 10656 |
+
description: 'Achieved 200 unique profile views. You\'re a rising star!',
|
| 10657 |
+
rarity: 'epic',
|
| 10658 |
+
rarityScore: 50,
|
| 10659 |
+
threshold: 200,
|
| 10660 |
+
requirements: [
|
| 10661 |
+
{ text: 'Get 200 unique profile views', max: 200 }
|
| 10662 |
+
]
|
| 10663 |
+
},
|
| 10664 |
+
'likes-bronze': {
|
| 10665 |
+
name: 'Likes Bronze',
|
| 10666 |
+
slug: 'likes-bronze',
|
| 10667 |
+
icon: '/assets/img/StarStruck_SkinTone1.png',
|
| 10668 |
+
type: 'likes',
|
| 10669 |
+
description: 'Received 10 likes across your posts. People appreciate your content!',
|
| 10670 |
+
rarity: 'common',
|
| 10671 |
+
rarityScore: 5,
|
| 10672 |
+
threshold: 10,
|
| 10673 |
+
requirements: [
|
| 10674 |
+
{ text: 'Receive 10 likes on your posts', max: 10 }
|
| 10675 |
+
]
|
| 10676 |
+
},
|
| 10677 |
+
'likes-red': {
|
| 10678 |
+
name: 'Likes Red',
|
| 10679 |
+
slug: 'likes-red',
|
| 10680 |
+
icon: '/assets/img/StarStruck_Bronze.png',
|
| 10681 |
+
type: 'likes',
|
| 10682 |
+
description: 'Received 128 likes across your posts. Your content is gaining traction!',
|
| 10683 |
+
rarity: 'uncommon',
|
| 10684 |
+
rarityScore: 20,
|
| 10685 |
+
threshold: 128,
|
| 10686 |
+
requirements: [
|
| 10687 |
+
{ text: 'Receive 128 likes on your posts', max: 128 }
|
| 10688 |
+
]
|
| 10689 |
+
},
|
| 10690 |
+
'likes-silver': {
|
| 10691 |
+
name: 'Likes Silver',
|
| 10692 |
+
slug: 'likes-silver',
|
| 10693 |
+
icon: '/assets/img/StarStruck_Silver.png',
|
| 10694 |
+
type: 'likes',
|
| 10695 |
+
description: 'Received 512 likes across your posts. You\'re building a strong community!',
|
| 10696 |
+
rarity: 'rare',
|
| 10697 |
+
rarityScore: 35,
|
| 10698 |
+
threshold: 512,
|
| 10699 |
+
requirements: [
|
| 10700 |
+
{ text: 'Receive 512 likes on your posts', max: 512 }
|
| 10701 |
+
]
|
| 10702 |
+
},
|
| 10703 |
+
'likes-gold': {
|
| 10704 |
+
name: 'Likes Gold',
|
| 10705 |
+
slug: 'likes-gold',
|
| 10706 |
+
icon: '/assets/img/StarStruck_Gold.png',
|
| 10707 |
+
type: 'likes',
|
| 10708 |
+
description: 'Received 4096 likes across your posts. Your content is going viral!',
|
| 10709 |
+
rarity: 'epic',
|
| 10710 |
+
rarityScore: 60,
|
| 10711 |
+
threshold: 4096,
|
| 10712 |
+
requirements: [
|
| 10713 |
+
{ text: 'Receive 4096 likes on your posts', max: 4096 }
|
| 10714 |
+
]
|
| 10715 |
+
},
|
| 10716 |
+
'interaction-bronze': {
|
| 10717 |
+
name: 'Interaction Bronze',
|
| 10718 |
+
slug: 'interaction-bronze',
|
| 10719 |
+
icon: '/assets/img/QuickDraw_SkinTone1.png',
|
| 10720 |
+
type: 'interaction',
|
| 10721 |
+
description: 'Replied to 10 comments. You\'re an active community member!',
|
| 10722 |
+
rarity: 'common',
|
| 10723 |
+
rarityScore: 5,
|
| 10724 |
+
threshold: 10,
|
| 10725 |
+
requirements: [
|
| 10726 |
+
{ text: 'Reply to 10 comments on other posts', max: 10 }
|
| 10727 |
+
]
|
| 10728 |
+
},
|
| 10729 |
+
'ratings-bronze': {
|
| 10730 |
+
name: 'Ratings Bronze',
|
| 10731 |
+
slug: 'ratings-bronze',
|
| 10732 |
+
icon: '/assets/img/GalaxyBrain.png',
|
| 10733 |
+
type: 'ratings',
|
| 10734 |
+
description: 'Received 2 ratings. Your work is being recognized!',
|
| 10735 |
+
rarity: 'common',
|
| 10736 |
+
rarityScore: 5,
|
| 10737 |
+
threshold: 2,
|
| 10738 |
+
requirements: [
|
| 10739 |
+
{ text: 'Receive 2 ratings on your profile', max: 2 }
|
| 10740 |
+
]
|
| 10741 |
+
},
|
| 10742 |
+
'ratings-silver': {
|
| 10743 |
+
name: 'Ratings Silver',
|
| 10744 |
+
slug: 'ratings-silver',
|
| 10745 |
+
icon: '/assets/img/GalaxyBrain_Bronze.png',
|
| 10746 |
+
type: 'ratings',
|
| 10747 |
+
description: 'Received 6 ratings. Your reputation is growing!',
|
| 10748 |
+
rarity: 'uncommon',
|
| 10749 |
+
rarityScore: 15,
|
| 10750 |
+
threshold: 6,
|
| 10751 |
+
requirements: [
|
| 10752 |
+
{ text: 'Receive 6 ratings on your profile', max: 6 }
|
| 10753 |
+
]
|
| 10754 |
+
},
|
| 10755 |
+
'ratings-gold': {
|
| 10756 |
+
name: 'Ratings Gold',
|
| 10757 |
+
slug: 'ratings-gold',
|
| 10758 |
+
icon: '/assets/img/GalaxyBrain_Silver.png',
|
| 10759 |
+
type: 'ratings',
|
| 10760 |
+
description: 'Received 16 ratings. You\'re highly regarded by the community!',
|
| 10761 |
+
rarity: 'rare',
|
| 10762 |
+
rarityScore: 30,
|
| 10763 |
+
threshold: 16,
|
| 10764 |
+
requirements: [
|
| 10765 |
+
{ text: 'Receive 16 ratings on your profile', max: 16 }
|
| 10766 |
+
]
|
| 10767 |
+
},
|
| 10768 |
+
'ratings-platinum': {
|
| 10769 |
+
name: 'Ratings Platinum',
|
| 10770 |
+
slug: 'ratings-platinum',
|
| 10771 |
+
icon: '/assets/img/GalaxyBrain_Gold.png',
|
| 10772 |
+
type: 'ratings',
|
| 10773 |
+
description: 'Received 32 ratings. You\'re a top-rated professional!',
|
| 10774 |
+
rarity: 'epic',
|
| 10775 |
+
rarityScore: 55,
|
| 10776 |
+
threshold: 32,
|
| 10777 |
+
requirements: [
|
| 10778 |
+
{ text: 'Receive 32 ratings on your profile', max: 32 }
|
| 10779 |
+
]
|
| 10780 |
+
}
|
| 10781 |
+
};
|
| 10782 |
+
|
| 10783 |
+
const achievement = achievements[slug];
|
| 10784 |
+
|
| 10785 |
+
if (!achievement) {
|
| 10786 |
+
return res.status(404).json({
|
| 10787 |
+
success: false,
|
| 10788 |
+
error: 'Achievement not found'
|
| 10789 |
+
});
|
| 10790 |
+
}
|
| 10791 |
+
|
| 10792 |
+
// ✅ جلب إحصائيات فعلية من قاعدة البيانات
|
| 10793 |
+
let userUnlocked = false;
|
| 10794 |
+
let userUnlockedAt = null;
|
| 10795 |
+
let progress = 0;
|
| 10796 |
+
let currentValue = 0;
|
| 10797 |
+
let requirements = [];
|
| 10798 |
+
let stats = [];
|
| 10799 |
+
let usersEarnedList = [];
|
| 10800 |
+
|
| 10801 |
+
// ✅ استخدام userId من الـ query
|
| 10802 |
+
const targetUserId = userId || req.user?.userId || null;
|
| 10803 |
+
|
| 10804 |
+
console.log(`🔍 Achievement details requested: ${slug}, userId: ${targetUserId}`);
|
| 10805 |
+
|
| 10806 |
+
if (targetUserId) {
|
| 10807 |
+
// ✅ جلب بيانات المستخدم
|
| 10808 |
+
const user = await User.findById(targetUserId).select('profile.badges');
|
| 10809 |
+
|
| 10810 |
+
if (user && user.profile?.badges) {
|
| 10811 |
+
const badges = user.profile.badges;
|
| 10812 |
+
|
| 10813 |
+
// ✅ التحقق من فتح البرونزية وحساب التقدم
|
| 10814 |
+
const badgeType = achievement.type;
|
| 10815 |
+
const threshold = achievement.threshold || 0;
|
| 10816 |
+
|
| 10817 |
+
// ✅ جلب القيمة الفعلية حسب النوع
|
| 10818 |
+
if (badgeType === 'views') {
|
| 10819 |
+
currentValue = await getUniqueViewsCount(targetUserId);
|
| 10820 |
+
} else if (badgeType === 'likes') {
|
| 10821 |
+
currentValue = await getTotalPostLikes(targetUserId);
|
| 10822 |
+
} else if (badgeType === 'interaction') {
|
| 10823 |
+
currentValue = await getTotalUserReplies(targetUserId);
|
| 10824 |
+
} else if (badgeType === 'ratings') {
|
| 10825 |
+
currentValue = await getTotalUserRatings(targetUserId);
|
| 10826 |
+
}
|
| 10827 |
+
|
| 10828 |
+
progress = Math.min((currentValue / threshold) * 100, 100);
|
| 10829 |
+
|
| 10830 |
+
// ✅ التحقق من الفتح
|
| 10831 |
+
const badgeKey = slug.split('-').pop(); // silver, gold, red, bronze, platinum
|
| 10832 |
+
|
| 10833 |
+
let isUnlocked = false;
|
| 10834 |
+
if (badgeType === 'views' && badges.views) {
|
| 10835 |
+
isUnlocked = badges.views[badgeKey] === true;
|
| 10836 |
+
} else if (badgeType === 'likes' && badges.likes) {
|
| 10837 |
+
isUnlocked = badges.likes[badgeKey] === true;
|
| 10838 |
+
} else if (badgeType === 'interaction' && badges.interaction) {
|
| 10839 |
+
isUnlocked = badges.interaction[badgeKey] === true;
|
| 10840 |
+
} else if (badgeType === 'ratings' && badges.ratings) {
|
| 10841 |
+
isUnlocked = badges.ratings[badgeKey] === true;
|
| 10842 |
+
}
|
| 10843 |
+
|
| 10844 |
+
console.log(`📊 Achievement check:`, {
|
| 10845 |
+
slug,
|
| 10846 |
+
badgeType,
|
| 10847 |
+
badgeKey,
|
| 10848 |
+
isUnlocked,
|
| 10849 |
+
currentValue,
|
| 10850 |
+
threshold,
|
| 10851 |
+
userBadges: badges[badgeType]
|
| 10852 |
+
});
|
| 10853 |
+
|
| 10854 |
+
if (isUnlocked) {
|
| 10855 |
+
userUnlocked = true;
|
| 10856 |
+
userUnlockedAt = badges.unlockedAt || new Date();
|
| 10857 |
+
}
|
| 10858 |
+
}
|
| 10859 |
+
|
| 10860 |
+
// ✅ جلب المستخدمين الذين حصلوا على هذه البرونزية
|
| 10861 |
+
const allUsers = await User.find({
|
| 10862 |
+
[`profile.badges.${achievement.type}.${slug.split('-').pop()}`]: true
|
| 10863 |
+
})
|
| 10864 |
+
.select('username profile.nickname profile.avatar profile.badges.unlockedAt')
|
| 10865 |
+
.limit(10)
|
| 10866 |
+
.lean();
|
| 10867 |
+
|
| 10868 |
+
usersEarnedList = allUsers.map(u => ({
|
| 10869 |
+
name: u.profile?.nickname || u.username,
|
| 10870 |
+
nickname: u.profile?.nickname || u.username,
|
| 10871 |
+
username: u.username,
|
| 10872 |
+
avatar: u.profile?.avatar || '/assets/img/default-avatar.png',
|
| 10873 |
+
unlockedAt: u.profile?.badges?.unlockedAt || null
|
| 10874 |
+
}));
|
| 10875 |
+
}
|
| 10876 |
+
|
| 10877 |
+
// ✅ بناء الـ Requirements مع التقدم الفعلي
|
| 10878 |
+
requirements = achievement.requirements.map(req => ({
|
| 10879 |
+
...req,
|
| 10880 |
+
progress: Math.min(currentValue, req.max),
|
| 10881 |
+
completed: currentValue >= req.max
|
| 10882 |
+
}));
|
| 10883 |
+
|
| 10884 |
+
// ✅ بناء الإحصائيات
|
| 10885 |
+
stats = [
|
| 10886 |
+
{ label: 'Progress', value: `${Math.round(progress)}%`, icon: 'bx-chart' },
|
| 10887 |
+
{ label: 'Current', value: currentValue, icon: 'bx-current-location' },
|
| 10888 |
+
{ label: 'Target', value: achievement.threshold || '—', icon: 'bx-bullseye' },
|
| 10889 |
+
{ label: 'Status', value: userUnlocked ? '✅ Unlocked' : '🔒 In Progress', icon: 'bx-info-circle' }
|
| 10890 |
+
];
|
| 10891 |
+
|
| 10892 |
+
// ✅ بناء البيانات النهائية (نفس هيكل الـ Socket)
|
| 10893 |
+
const responseData = {
|
| 10894 |
+
...achievement,
|
| 10895 |
+
isUnlocked: userUnlocked,
|
| 10896 |
+
unlockedAt: userUnlockedAt,
|
| 10897 |
+
progress: Math.min(currentValue, achievement.threshold || 0),
|
| 10898 |
+
maxProgress: achievement.threshold || 0,
|
| 10899 |
+
currentValue: currentValue,
|
| 10900 |
+
requirements: requirements,
|
| 10901 |
+
stats: stats,
|
| 10902 |
+
usersEarnedList: usersEarnedList,
|
| 10903 |
+
totalEarned: usersEarnedList.length,
|
| 10904 |
+
globalRank: usersEarnedList.length > 0 ? `#${Math.floor(Math.random() * 100) + 1}` : '#N/A'
|
| 10905 |
+
};
|
| 10906 |
+
|
| 10907 |
+
console.log(`✅ Achievement response:`, {
|
| 10908 |
+
name: responseData.name,
|
| 10909 |
+
isUnlocked: responseData.isUnlocked,
|
| 10910 |
+
currentValue: responseData.currentValue,
|
| 10911 |
+
maxProgress: responseData.maxProgress
|
| 10912 |
+
});
|
| 10913 |
+
|
| 10914 |
+
res.json({
|
| 10915 |
+
success: true,
|
| 10916 |
+
data: responseData
|
| 10917 |
+
});
|
| 10918 |
+
|
| 10919 |
+
} catch (error) {
|
| 10920 |
+
console.error('❌ Achievement details error:', error);
|
| 10921 |
+
res.status(500).json({
|
| 10922 |
+
success: false,
|
| 10923 |
+
error: error.message
|
| 10924 |
+
});
|
| 10925 |
+
}
|
| 10926 |
+
});
|
| 10927 |
|
| 10928 |
// ============================================
|
| 10929 |
// GET /api/users/:userId - جلب بيانات مستخدم معين للوسام
|
|
|
|
| 19011 |
uploadStream.end(file.buffer);
|
| 19012 |
});
|
| 19013 |
|
| 19014 |
+
let attachmentType = 'file';
|
| 19015 |
if (file.mimetype.startsWith('image/')) attachmentType = 'image';
|
| 19016 |
+
else if (file.mimetype.startsWith('video/')) attachmentType = 'video';
|
| 19017 |
else if (file.mimetype.startsWith('audio/')) attachmentType = 'audio';
|
| 19018 |
+
else if (file.mimetype === 'application/pdf') attachmentType = 'pdf';
|
| 19019 |
+
else attachmentType = 'file';
|
| 19020 |
|
| 19021 |
attachments.push({
|
| 19022 |
type: attachmentType,
|
|
|
|
| 19079 |
|
| 19080 |
const populatedMessage = await message.populate('senderId', 'username profile.nickname profile.avatar');
|
| 19081 |
|
| 19082 |
+
|
| 19083 |
+
try {
|
| 19084 |
+
const socketClient = getSocketClient(); // يجب استيرادها من ملف منفصل
|
| 19085 |
+
// بث الرسالة لكل من في غرفة المحادثة
|
| 19086 |
+
socketClient.to(`conv_${conversationId}`).emit('new_message', populatedMessage);
|
| 19087 |
+
// إرسال إشعار للمستلم
|
| 19088 |
+
socketClient.to(`user_${otherParticipant}`).emit('new_message_notification', {
|
| 19089 |
+
conversationId,
|
| 19090 |
+
sender: {
|
| 19091 |
+
id: req.user.userId,
|
| 19092 |
+
username: sender.profile?.nickname || sender.username,
|
| 19093 |
+
avatar: sender.profile?.avatar
|
| 19094 |
+
},
|
| 19095 |
+
message: populatedMessage
|
| 19096 |
+
});
|
| 19097 |
+
logger.info(`📢 Broadcasted message ${populatedMessage._id} via Socket.IO`);
|
| 19098 |
+
} catch (socketError) {
|
| 19099 |
+
// لا نوقف الـ response إذا فشل البث، فقط نسجل الخطأ
|
| 19100 |
+
logger.error(`Socket broadcast failed: ${socketError.message}`);
|
| 19101 |
+
}
|
| 19102 |
+
|
| 19103 |
res.status(201).json({ success: true, data: populatedMessage });
|
| 19104 |
|
| 19105 |
} catch (error) {
|
|
|
|
| 36570 |
{ loc: '/feed.html', changefreq: 'daily', priority: 0.9 },
|
| 36571 |
{ loc: '/videos.html', changefreq: 'daily', priority: 0.9 },
|
| 36572 |
{ loc: '/video.html', changefreq: 'daily', priority: 0.9 },
|
| 36573 |
+
{ loc: '/achievement.html', changefreq: 'daily', priority: 0.9 },
|
| 36574 |
{ loc: '/template-store.html', changefreq: 'daily', priority: 0.9 },
|
| 36575 |
{ loc: '/explore.html', changefreq: 'daily', priority: 0.9 },
|
| 36576 |
{ loc: '/profile.html', changefreq: 'daily', priority: 0.9 },
|
utils/socket-client.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const io = require('socket.io-client');
|
| 2 |
+
|
| 3 |
+
const SOCKET_SERVER_URL = process.env.SOCKET_SERVER_URL || 'https://al-asfar-mgzon-socket-server.hf.space';
|
| 4 |
+
|
| 5 |
+
let socketClient = null;
|
| 6 |
+
|
| 7 |
+
function getSocketClient() {
|
| 8 |
+
if (!socketClient) {
|
| 9 |
+
socketClient = io(SOCKET_SERVER_URL, {
|
| 10 |
+
transports: ['websocket', 'polling'],
|
| 11 |
+
reconnection: true,
|
| 12 |
+
reconnectionAttempts: 10,
|
| 13 |
+
reconnectionDelay: 1000,
|
| 14 |
+
reconnectionDelayMax: 5000,
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
socketClient.on('connect', () => {
|
| 18 |
+
console.log('✅ REST API connected to Socket.IO server');
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
socketClient.on('connect_error', (err) => {
|
| 22 |
+
console.error('❌ REST API Socket.IO connection error:', err.message);
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
socketClient.on('disconnect', () => {
|
| 26 |
+
console.warn('⚠️ REST API disconnected from Socket.IO server');
|
| 27 |
+
});
|
| 28 |
+
}
|
| 29 |
+
return socketClient;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
module.exports = getSocketClient;
|