├── .gitignore ├── public ├── js │ ├── topic.js │ └── tinymce │ │ ├── skins │ │ ├── ui │ │ │ ├── oxide │ │ │ │ ├── fonts │ │ │ │ │ └── tinymce-mobile.woff │ │ │ │ ├── content.mobile.min.css │ │ │ │ └── content.mobile.css │ │ │ └── oxide-dark │ │ │ │ ├── fonts │ │ │ │ └── tinymce-mobile.woff │ │ │ │ ├── content.mobile.min.css │ │ │ │ └── content.mobile.css │ │ └── content │ │ │ ├── default │ │ │ ├── content.min.css │ │ │ └── content.css │ │ │ ├── writer │ │ │ ├── content.min.css │ │ │ └── content.css │ │ │ ├── document │ │ │ ├── content.min.css │ │ │ └── content.css │ │ │ └── dark │ │ │ ├── content.min.css │ │ │ └── content.css │ │ ├── plugins │ │ ├── hr │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── toc │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── code │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── help │ │ │ └── index.js │ │ ├── image │ │ │ └── index.js │ │ ├── link │ │ │ └── index.js │ │ ├── lists │ │ │ └── index.js │ │ ├── media │ │ │ └── index.js │ │ ├── paste │ │ │ └── index.js │ │ ├── print │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── save │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── table │ │ │ └── index.js │ │ ├── anchor │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── bbcode │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── advlist │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── charmap │ │ │ └── index.js │ │ ├── preview │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── autolink │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── autosave │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── fullpage │ │ │ └── index.js │ │ ├── tabfocus │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── template │ │ │ └── index.js │ │ ├── autoresize │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── codesample │ │ │ └── index.js │ │ ├── emoticons │ │ │ └── index.js │ │ ├── fullscreen │ │ │ └── index.js │ │ ├── imagetools │ │ │ └── index.js │ │ ├── importcss │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── pagebreak │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── quickbars │ │ │ └── index.js │ │ ├── textcolor │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── wordcount │ │ │ └── index.js │ │ ├── colorpicker │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── contextmenu │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── nonbreaking │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── noneditable │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── textpattern │ │ │ └── index.js │ │ ├── visualchars │ │ │ └── index.js │ │ ├── legacyoutput │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── spellchecker │ │ │ └── index.js │ │ ├── visualblocks │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── searchreplace │ │ │ └── index.js │ │ ├── directionality │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ └── insertdatetime │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── icons │ │ └── default │ │ │ └── index.js │ │ ├── themes │ │ ├── mobile │ │ │ └── index.js │ │ └── silver │ │ │ └── index.js │ │ ├── bower.json │ │ ├── composer.json │ │ ├── package.json │ │ └── readme.md ├── images │ ├── icons │ │ ├── talk.png │ │ ├── 753890-32.png │ │ ├── forum-32.png │ │ ├── timeIcon.png │ │ ├── user64px.png │ │ ├── 1954526-16.png │ │ ├── courseIcon.png │ │ ├── homeworkIcon.png │ │ ├── infoIcon24px.png │ │ ├── studentIcon.png │ │ ├── followIcon20px.png │ │ ├── followIcon24px.png │ │ ├── followIcon32px.png │ │ ├── universityIcon.png │ │ ├── announcementIcon.png │ │ ├── iconProfile24px.png │ │ ├── iconWriteAnswer.png │ │ ├── unfollowIcon20px.png │ │ ├── unfollowIcon24px.png │ │ ├── unfollowIcon32px.png │ │ ├── universityTitleIcon.png │ │ ├── iconfinder_blog_46779.png │ │ ├── iconfinder_info-blog_46810.png │ │ ├── iconfinder_profile-filled_299075.png │ │ ├── iconfinder_resolutions-08_897241.png │ │ ├── iconfinder_Closed_Book_Icon_1741323.png │ │ ├── liked.svg │ │ ├── not-liked.svg │ │ ├── unfollow.svg │ │ └── follow.svg │ ├── readme-images │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 9.png │ │ ├── EOL.PNG │ │ └── mongo-import-error.PNG │ ├── topics-images │ │ ├── image-1593676345129.jpg │ │ ├── image-1593676576143.png │ │ ├── image-1593676829053.png │ │ ├── image-1593677023678.png │ │ ├── image-1593677078061.png │ │ ├── image-1593677344763.png │ │ ├── image-1593680383311.png │ │ ├── image-1593680415229.png │ │ ├── image-1593680454047.png │ │ ├── image-1593680500501.png │ │ ├── image-1593680595679.jpg │ │ ├── image-1593676282305.jpeg │ │ └── image-1593680561250.jpeg │ └── users-images │ │ ├── image-1593680158999.jpg │ │ ├── image-1593680220764.jpg │ │ ├── image-1593680262984.jpg │ │ └── image-1593680290286.jpg ├── webfonts │ ├── fa-brands-400.eot │ ├── fa-brands-400.ttf │ ├── fa-solid-900.eot │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ └── fa-solid-900.woff2 └── css │ ├── modified-bootstrap.css │ └── style.css ├── views ├── partials │ ├── profile-includes │ │ ├── inc.hbs │ │ ├── user-questions.hbs │ │ ├── user-answers.hbs │ │ ├── user-topics-followed.hbs │ │ └── user-topics-created.hbs │ └── navbar.hbs ├── error.hbs ├── layouts │ └── main.hbs ├── login.hbs ├── write-answer.hbs ├── ask-question.hbs ├── create-topic.hbs ├── register.hbs └── profile.hbs ├── .dockerignore ├── Dockerfile ├── routes ├── answersRoutes.js ├── questionsRoutes.js ├── usersRoutes.js └── topicsRoutes.js ├── .env ├── mongodb-database └── import-data.sh ├── models ├── answer.js ├── question.js ├── topic.js └── user.js ├── package.json ├── utils └── passport-config.js ├── controllers ├── answersController.js ├── questionsController.js └── usersController.js ├── bin └── www ├── docker-compose.yaml └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /public/js/topic.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /views/partials/profile-includes/inc.hbs: -------------------------------------------------------------------------------- 1 | alae -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .gitignore 3 | .git/ 4 | Dockerfile -------------------------------------------------------------------------------- /public/images/icons/talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/talk.png -------------------------------------------------------------------------------- /public/images/icons/753890-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/753890-32.png -------------------------------------------------------------------------------- /public/images/icons/forum-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/forum-32.png -------------------------------------------------------------------------------- /public/images/icons/timeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/timeIcon.png -------------------------------------------------------------------------------- /public/images/icons/user64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/user64px.png -------------------------------------------------------------------------------- /public/images/readme-images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/1.png -------------------------------------------------------------------------------- /public/images/readme-images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/2.png -------------------------------------------------------------------------------- /public/images/readme-images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/3.png -------------------------------------------------------------------------------- /public/images/readme-images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/4.png -------------------------------------------------------------------------------- /public/images/readme-images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/5.png -------------------------------------------------------------------------------- /public/images/readme-images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/6.png -------------------------------------------------------------------------------- /public/images/readme-images/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/7.png -------------------------------------------------------------------------------- /public/images/readme-images/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/8.png -------------------------------------------------------------------------------- /public/images/readme-images/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/9.png -------------------------------------------------------------------------------- /public/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /public/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /public/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /public/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /public/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /public/images/icons/1954526-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/1954526-16.png -------------------------------------------------------------------------------- /public/images/icons/courseIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/courseIcon.png -------------------------------------------------------------------------------- /public/images/icons/homeworkIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/homeworkIcon.png -------------------------------------------------------------------------------- /public/images/icons/infoIcon24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/infoIcon24px.png -------------------------------------------------------------------------------- /public/images/icons/studentIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/studentIcon.png -------------------------------------------------------------------------------- /public/images/readme-images/EOL.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/EOL.PNG -------------------------------------------------------------------------------- /public/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /public/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /public/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /public/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /public/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /public/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /public/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /public/images/icons/followIcon20px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/followIcon20px.png -------------------------------------------------------------------------------- /public/images/icons/followIcon24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/followIcon24px.png -------------------------------------------------------------------------------- /public/images/icons/followIcon32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/followIcon32px.png -------------------------------------------------------------------------------- /public/images/icons/universityIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/universityIcon.png -------------------------------------------------------------------------------- /public/images/icons/announcementIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/announcementIcon.png -------------------------------------------------------------------------------- /public/images/icons/iconProfile24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconProfile24px.png -------------------------------------------------------------------------------- /public/images/icons/iconWriteAnswer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconWriteAnswer.png -------------------------------------------------------------------------------- /public/images/icons/unfollowIcon20px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/unfollowIcon20px.png -------------------------------------------------------------------------------- /public/images/icons/unfollowIcon24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/unfollowIcon24px.png -------------------------------------------------------------------------------- /public/images/icons/unfollowIcon32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/unfollowIcon32px.png -------------------------------------------------------------------------------- /public/images/icons/universityTitleIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/universityTitleIcon.png -------------------------------------------------------------------------------- /public/images/icons/iconfinder_blog_46779.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconfinder_blog_46779.png -------------------------------------------------------------------------------- /public/css/modified-bootstrap.css: -------------------------------------------------------------------------------- 1 | .card-link { 2 | color: #445252; 3 | } 4 | 5 | .card-link:hover { 6 | text-decoration: none; 7 | color: blue; 8 | } 9 | -------------------------------------------------------------------------------- /public/images/icons/iconfinder_info-blog_46810.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconfinder_info-blog_46810.png -------------------------------------------------------------------------------- /public/images/readme-images/mongo-import-error.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/mongo-import-error.PNG -------------------------------------------------------------------------------- /public/images/topics-images/image-1593676345129.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593676345129.jpg -------------------------------------------------------------------------------- /public/images/topics-images/image-1593676576143.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593676576143.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593676829053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593676829053.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593677023678.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593677023678.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593677078061.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593677078061.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593677344763.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593677344763.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680383311.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680383311.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680415229.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680415229.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680454047.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680454047.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680500501.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680500501.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680595679.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680595679.jpg -------------------------------------------------------------------------------- /public/images/users-images/image-1593680158999.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/users-images/image-1593680158999.jpg -------------------------------------------------------------------------------- /public/images/users-images/image-1593680220764.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/users-images/image-1593680220764.jpg -------------------------------------------------------------------------------- /public/images/users-images/image-1593680262984.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/users-images/image-1593680262984.jpg -------------------------------------------------------------------------------- /public/images/users-images/image-1593680290286.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/users-images/image-1593680290286.jpg -------------------------------------------------------------------------------- /views/error.hbs: -------------------------------------------------------------------------------- 1 |
2 |

{{message}}

3 |

{{error.status}}

4 |
{{error.stack}}
5 | 6 |
-------------------------------------------------------------------------------- /public/images/topics-images/image-1593676282305.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593676282305.jpeg -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680561250.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680561250.jpeg -------------------------------------------------------------------------------- /public/images/icons/iconfinder_profile-filled_299075.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconfinder_profile-filled_299075.png -------------------------------------------------------------------------------- /public/images/icons/iconfinder_resolutions-08_897241.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconfinder_resolutions-08_897241.png -------------------------------------------------------------------------------- /public/js/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/js/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /public/images/icons/iconfinder_Closed_Book_Icon_1741323.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconfinder_Closed_Book_Icon_1741323.png -------------------------------------------------------------------------------- /public/js/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/js/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine3.9 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json . 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 3000 12 | 13 | CMD [ "npm", "run", "startdev" ] -------------------------------------------------------------------------------- /public/js/tinymce/plugins/hr/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "hr" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/hr') 5 | // ES2015: 6 | // import 'tinymce/plugins/hr' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/toc/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "toc" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/toc') 5 | // ES2015: 6 | // import 'tinymce/plugins/toc' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/icons/default/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "default" icons for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/icons/default') 5 | // ES2015: 6 | // import 'tinymce/icons/default' 7 | require('./icons.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/code/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "code" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/code') 5 | // ES2015: 6 | // import 'tinymce/plugins/code' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/help/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "help" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/help') 5 | // ES2015: 6 | // import 'tinymce/plugins/help' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/image/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "image" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/image') 5 | // ES2015: 6 | // import 'tinymce/plugins/image' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/link/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "link" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/link') 5 | // ES2015: 6 | // import 'tinymce/plugins/link' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/lists/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "lists" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/lists') 5 | // ES2015: 6 | // import 'tinymce/plugins/lists' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/media/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "media" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/media') 5 | // ES2015: 6 | // import 'tinymce/plugins/media' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/paste/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "paste" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/paste') 5 | // ES2015: 6 | // import 'tinymce/plugins/paste' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/print/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "print" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/print') 5 | // ES2015: 6 | // import 'tinymce/plugins/print' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/save/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "save" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/save') 5 | // ES2015: 6 | // import 'tinymce/plugins/save' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/table/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "table" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/table') 5 | // ES2015: 6 | // import 'tinymce/plugins/table' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/themes/mobile/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "mobile" theme for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/themes/mobile') 5 | // ES2015: 6 | // import 'tinymce/themes/mobile' 7 | require('./theme.js'); -------------------------------------------------------------------------------- /public/js/tinymce/themes/silver/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "silver" theme for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/themes/silver') 5 | // ES2015: 6 | // import 'tinymce/themes/silver' 7 | require('./theme.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/anchor/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "anchor" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/anchor') 5 | // ES2015: 6 | // import 'tinymce/plugins/anchor' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/bbcode/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "bbcode" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/bbcode') 5 | // ES2015: 6 | // import 'tinymce/plugins/bbcode' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/advlist/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "advlist" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/advlist') 5 | // ES2015: 6 | // import 'tinymce/plugins/advlist' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/charmap/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "charmap" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/charmap') 5 | // ES2015: 6 | // import 'tinymce/plugins/charmap' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/preview/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "preview" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/preview') 5 | // ES2015: 6 | // import 'tinymce/plugins/preview' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/autolink/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "autolink" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/autolink') 5 | // ES2015: 6 | // import 'tinymce/plugins/autolink' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/autosave/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "autosave" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/autosave') 5 | // ES2015: 6 | // import 'tinymce/plugins/autosave' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/fullpage/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "fullpage" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/fullpage') 5 | // ES2015: 6 | // import 'tinymce/plugins/fullpage' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/tabfocus/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "tabfocus" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/tabfocus') 5 | // ES2015: 6 | // import 'tinymce/plugins/tabfocus' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/template/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "template" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/template') 5 | // ES2015: 6 | // import 'tinymce/plugins/template' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/autoresize/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "autoresize" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/autoresize') 5 | // ES2015: 6 | // import 'tinymce/plugins/autoresize' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/codesample/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "codesample" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/codesample') 5 | // ES2015: 6 | // import 'tinymce/plugins/codesample' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/emoticons/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "emoticons" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/emoticons') 5 | // ES2015: 6 | // import 'tinymce/plugins/emoticons' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/fullscreen/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "fullscreen" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/fullscreen') 5 | // ES2015: 6 | // import 'tinymce/plugins/fullscreen' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/imagetools/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "imagetools" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/imagetools') 5 | // ES2015: 6 | // import 'tinymce/plugins/imagetools' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/importcss/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "importcss" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/importcss') 5 | // ES2015: 6 | // import 'tinymce/plugins/importcss' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/pagebreak/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "pagebreak" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/pagebreak') 5 | // ES2015: 6 | // import 'tinymce/plugins/pagebreak' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/quickbars/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "quickbars" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/quickbars') 5 | // ES2015: 6 | // import 'tinymce/plugins/quickbars' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/textcolor/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "textcolor" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/textcolor') 5 | // ES2015: 6 | // import 'tinymce/plugins/textcolor' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/wordcount/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "wordcount" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/wordcount') 5 | // ES2015: 6 | // import 'tinymce/plugins/wordcount' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/colorpicker/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "colorpicker" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/colorpicker') 5 | // ES2015: 6 | // import 'tinymce/plugins/colorpicker' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/contextmenu/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "contextmenu" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/contextmenu') 5 | // ES2015: 6 | // import 'tinymce/plugins/contextmenu' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/nonbreaking/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "nonbreaking" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/nonbreaking') 5 | // ES2015: 6 | // import 'tinymce/plugins/nonbreaking' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/noneditable/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "noneditable" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/noneditable') 5 | // ES2015: 6 | // import 'tinymce/plugins/noneditable' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/textpattern/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "textpattern" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/textpattern') 5 | // ES2015: 6 | // import 'tinymce/plugins/textpattern' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/visualchars/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "visualchars" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/visualchars') 5 | // ES2015: 6 | // import 'tinymce/plugins/visualchars' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/legacyoutput/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "legacyoutput" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/legacyoutput') 5 | // ES2015: 6 | // import 'tinymce/plugins/legacyoutput' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/spellchecker/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "spellchecker" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/spellchecker') 5 | // ES2015: 6 | // import 'tinymce/plugins/spellchecker' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/visualblocks/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "visualblocks" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/visualblocks') 5 | // ES2015: 6 | // import 'tinymce/plugins/visualblocks' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/searchreplace/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "searchreplace" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/searchreplace') 5 | // ES2015: 6 | // import 'tinymce/plugins/searchreplace' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/directionality/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "directionality" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/directionality') 5 | // ES2015: 6 | // import 'tinymce/plugins/directionality' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/insertdatetime/index.js: -------------------------------------------------------------------------------- 1 | // Exports the "insertdatetime" plugin for usage with module loaders 2 | // Usage: 3 | // CommonJS: 4 | // require('tinymce/plugins/insertdatetime') 5 | // ES2015: 6 | // import 'tinymce/plugins/insertdatetime' 7 | require('./plugin.js'); -------------------------------------------------------------------------------- /routes/answersRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const router = express.Router() 3 | const usersController = require("./../controllers/usersController") 4 | const answersController = require("./../controllers/answersController") 5 | 6 | router.post("/like-unlike", usersController.checkAuthenticated, answersController.handleLikingUnliking) 7 | module.exports = router 8 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | MONGODB_SERVER=mongodb #service name of mongo 2 | MONGODB_USER=root 3 | MONGODB_PASSWORD=secret 4 | MONGODB_DATABASE=social_network_db 5 | MONGODB_DOCKER_PORT=27017 6 | MONGODB_HOST_PORT=27018 7 | 8 | NODE_DOCKER_PORT=3000 9 | NODE_HOST_PORT=3001 10 | 11 | MONGO_EXPRESS_DOCKER_PORT=8081 12 | MONGO_EXPRESS_HOST_PORT=8082 13 | 14 | NETWORK_NAME=node_mongo_mongoexpress_network -------------------------------------------------------------------------------- /public/js/tinymce/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinymce", 3 | "description": "Web based JavaScript HTML WYSIWYG editor control.", 4 | "license": "LGPL-2.1", 5 | "keywords": [ 6 | "editor", 7 | "wysiwyg", 8 | "tinymce", 9 | "richtext", 10 | "javascript", 11 | "html" 12 | ], 13 | "homepage": "http://www.tinymce.com", 14 | "ignore": [ 15 | "readme.md", 16 | "composer.json", 17 | "package.json", 18 | ".npmignore", 19 | "changelog.txt" 20 | ] 21 | } -------------------------------------------------------------------------------- /mongodb-database/import-data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mongoimport --db social_network_db --collection users --drop --file /docker-entrypoint-initdb.d/users.json --jsonArray; 3 | mongoimport --db social_network_db --collection questions --drop --file /docker-entrypoint-initdb.d/questions.json --jsonArray; 4 | mongoimport --db social_network_db --collection answers --drop --file /docker-entrypoint-initdb.d/answers.json --jsonArray; 5 | mongoimport --db social_network_db --collection topics --drop --file /docker-entrypoint-initdb.d/topics.json --jsonArray; -------------------------------------------------------------------------------- /routes/questionsRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const router = express.Router() 3 | const usersController = require("./../controllers/usersController") 4 | const questionsController = require("./../controllers/questionsController") 5 | 6 | router.get("/:id", questionsController.getQuestionById) 7 | 8 | router.get("/:id/answer", usersController.checkAuthenticated, questionsController.getCreateAnswerPage) 9 | router.post("/:id/answer", usersController.checkAuthenticated, questionsController.handleCreateAnswer) 10 | 11 | module.exports = router 12 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/textcolor/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(o){"use strict";var i=tinymce.util.Tools.resolve("tinymce.PluginManager");!function n(){i.add("textcolor",function(){o.console.warn("Text color plugin is now built in to the core editor, please remove it from your editor configuration")})}()}(window); -------------------------------------------------------------------------------- /public/js/tinymce/skins/ui/oxide/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} 8 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/colorpicker/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(o){"use strict";var i=tinymce.util.Tools.resolve("tinymce.PluginManager");!function n(){i.add("colorpicker",function(){o.console.warn("Color picker plugin is now built in to the core editor, please remove it from your editor configuration")})}()}(window); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/contextmenu/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(n){"use strict";var o=tinymce.util.Tools.resolve("tinymce.PluginManager");!function e(){o.add("contextmenu",function(){n.console.warn("Context menu plugin is now built in to the core editor, please remove it from your editor configuration")})}()}(window); -------------------------------------------------------------------------------- /public/js/tinymce/skins/ui/oxide-dark/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} 8 | -------------------------------------------------------------------------------- /models/answer.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const answerSchema = mongoose.Schema({ 4 | content: { 5 | type: String, 6 | required: true 7 | }, 8 | 9 | creationDate: { 10 | type: Date, 11 | required: true, 12 | default: Date.now 13 | }, 14 | user: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | ref: "User" 17 | }, 18 | usersWhoLike: [ 19 | { 20 | type: mongoose.Schema.Types.ObjectId, 21 | ref: "User" 22 | } 23 | ], 24 | question: { 25 | type: mongoose.Schema.Types.ObjectId, 26 | ref: "Question" 27 | } 28 | }) 29 | 30 | module.exports = mongoose.model("Answer", answerSchema) 31 | -------------------------------------------------------------------------------- /models/question.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const questionSchema = mongoose.Schema({ 4 | content: { 5 | type: String, 6 | required: true 7 | }, 8 | 9 | creationDate: { 10 | type: Date, 11 | required: true, 12 | default: Date.now 13 | }, 14 | user: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | required: true, 17 | ref: "User" 18 | }, 19 | topic: { 20 | type: mongoose.Schema.Types.ObjectId, 21 | required: true, 22 | ref: "Topic" 23 | }, 24 | answers: [ 25 | { 26 | type: mongoose.Schema.Types.ObjectId, 27 | ref: "Answer" 28 | } 29 | ] 30 | }) 31 | 32 | module.exports = mongoose.model("Question", questionSchema) 33 | -------------------------------------------------------------------------------- /models/topic.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const topicSchema = mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true, 7 | unique: true 8 | }, 9 | description: { 10 | type: String 11 | }, 12 | imageName: { 13 | type: String, 14 | required: true 15 | }, 16 | creationDate: { 17 | type: Date, 18 | required: true, 19 | default: Date.now 20 | }, 21 | user: { 22 | type: mongoose.Schema.Types.ObjectId, 23 | ref: "User" 24 | }, 25 | questions: [ 26 | { 27 | type: mongoose.Schema.Types.ObjectId, 28 | ref: "Question" 29 | } 30 | ], 31 | usersFollowers: [ 32 | { 33 | type: mongoose.Schema.Types.ObjectId, 34 | ref: "User" 35 | } 36 | ] 37 | }) 38 | 39 | module.exports = mongoose.model("Topic", topicSchema) 40 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/textcolor/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function (domGlobals) { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | function Plugin () { 15 | global.add('textcolor', function () { 16 | domGlobals.console.warn('Text color plugin is now built in to the core editor, please remove it from your editor configuration'); 17 | }); 18 | } 19 | 20 | Plugin(); 21 | 22 | }(window)); 23 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/colorpicker/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function (domGlobals) { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | function Plugin () { 15 | global.add('colorpicker', function () { 16 | domGlobals.console.warn('Color picker plugin is now built in to the core editor, please remove it from your editor configuration'); 17 | }); 18 | } 19 | 20 | Plugin(); 21 | 22 | }(window)); 23 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/contextmenu/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function (domGlobals) { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | function Plugin () { 15 | global.add('contextmenu', function () { 16 | domGlobals.console.warn('Context menu plugin is now built in to the core editor, please remove it from your editor configuration'); 17 | }); 18 | } 19 | 20 | Plugin(); 21 | 22 | }(window)); 23 | -------------------------------------------------------------------------------- /views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ capitalize title }} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{> navbar}} 19 | {{{body}}} 20 | 21 | 22 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/hr/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager");!function o(){n.add("hr",function(n){var o,t;(o=n).addCommand("InsertHorizontalRule",function(){o.execCommand("mceInsertContent",!1,"
")}),(t=n).ui.registry.addButton("hr",{icon:"horizontal-rule",tooltip:"Horizontal line",onAction:function(){return t.execCommand("InsertHorizontalRule")}}),t.ui.registry.addMenuItem("hr",{icon:"horizontal-rule",text:"Horizontal line",onAction:function(){return t.execCommand("InsertHorizontalRule")}})})}()}(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "startdev": "nodemon server.js" 7 | }, 8 | "dependencies": { 9 | "@handlebars/allow-prototype-access": "^1.0.3", 10 | "bcrypt": "^5.0.0", 11 | "cookie-parser": "~1.4.4", 12 | "debug": "~2.6.9", 13 | "dotenv": "^8.2.0", 14 | "express": "~4.16.1", 15 | "express-flash": "0.0.2", 16 | "express-handlebars": "^4.0.4", 17 | "express-session": "^1.17.1", 18 | "express-validator": "^6.6.0", 19 | "handlebars": "^4.7.6", 20 | "hbs": "^4.1.1", 21 | "http-errors": "~1.6.3", 22 | "method-override": "^3.0.0", 23 | "mongoose": "^5.9.19", 24 | "morgan": "~1.9.1", 25 | "multer": "^1.4.2", 26 | "passport": "^0.4.1", 27 | "passport-local": "^1.0.0", 28 | "tinymce": "^5.3.2" 29 | }, 30 | "devDependencies": { 31 | "nodemon": "^2.0.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/js/tinymce/skins/ui/oxide/content.mobile.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection { 8 | /* Note: this file is used inside the content, so isn't part of theming */ 9 | background-color: green; 10 | display: inline-block; 11 | opacity: 0.5; 12 | position: absolute; 13 | } 14 | body { 15 | -webkit-text-size-adjust: none; 16 | } 17 | body img { 18 | /* this is related to the content margin */ 19 | max-width: 96vw; 20 | } 21 | body table img { 22 | max-width: 95%; 23 | } 24 | body { 25 | font-family: sans-serif; 26 | } 27 | table { 28 | border-collapse: collapse; 29 | } 30 | -------------------------------------------------------------------------------- /public/js/tinymce/skins/ui/oxide-dark/content.mobile.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection { 8 | /* Note: this file is used inside the content, so isn't part of theming */ 9 | background-color: green; 10 | display: inline-block; 11 | opacity: 0.5; 12 | position: absolute; 13 | } 14 | body { 15 | -webkit-text-size-adjust: none; 16 | } 17 | body img { 18 | /* this is related to the content margin */ 19 | max-width: 96vw; 20 | } 21 | body table img { 22 | max-width: 95%; 23 | } 24 | body { 25 | font-family: sans-serif; 26 | } 27 | table { 28 | border-collapse: collapse; 29 | } 30 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/print/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),e=tinymce.util.Tools.resolve("tinymce.Env");!function t(){n.add("print",function(n){var t,i;(t=n).addCommand("mcePrint",function(){e.browser.isIE()?t.getDoc().execCommand("print",!1,null):t.getWin().print()}),(i=n).ui.registry.addButton("print",{icon:"print",tooltip:"Print",onAction:function(){return i.execCommand("mcePrint")}}),i.ui.registry.addMenuItem("print",{text:"Print...",icon:"print",onAction:function(){return i.execCommand("mcePrint")}}),n.addShortcut("Meta+P","","mcePrint")})}()}(); -------------------------------------------------------------------------------- /public/js/tinymce/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinymce/tinymce", 3 | "version": "5.3.2", 4 | "description": "Web based JavaScript HTML WYSIWYG editor control.", 5 | "license": [ 6 | "LGPL-2.1-only" 7 | ], 8 | "keywords": [ 9 | "editor", 10 | "wysiwyg", 11 | "tinymce", 12 | "richtext", 13 | "javascript", 14 | "html" 15 | ], 16 | "homepage": "http://www.tinymce.com", 17 | "type": "component", 18 | "extra": { 19 | "component": { 20 | "scripts": [ 21 | "tinymce.js", 22 | "plugins/*/plugin.js", 23 | "themes/*/theme.js", 24 | "themes/*/icons.js" 25 | ], 26 | "files": [ 27 | "tinymce.min.js", 28 | "plugins/*/plugin.min.js", 29 | "themes/*/theme.min.js", 30 | "skins/**", 31 | "icons/*/icons.min.js" 32 | ] 33 | } 34 | }, 35 | "archive": { 36 | "exclude": [ 37 | "readme.md", 38 | "bower.js", 39 | "package.json", 40 | ".npmignore", 41 | "changelog.txt" 42 | ] 43 | } 44 | } -------------------------------------------------------------------------------- /utils/passport-config.js: -------------------------------------------------------------------------------- 1 | const LocalStrategy = require("passport-local").Strategy 2 | const bcrypt = require("bcrypt") 3 | 4 | function initialize(passport, getUserByEmail, getUserById) { 5 | const authenticateUser = async (email, password, done) => { 6 | const user = await getUserByEmail(email) 7 | 8 | if (!user) { 9 | return done(null, false, { message: "No user with that email!" }) 10 | } 11 | 12 | try { 13 | if (await bcrypt.compare(password, user.password)) { 14 | return done(null, user) 15 | } else { 16 | return done(null, false, { message: "Password incorrect!" }) 17 | } 18 | } catch (e) { 19 | return done(e) 20 | } 21 | } 22 | 23 | passport.use(new LocalStrategy({ usernameField: "email" }, authenticateUser)) 24 | passport.serializeUser((user, done) => done(null, user._id)) 25 | passport.deserializeUser(async (id, done) => { 26 | return done(null, await getUserById(id)) 27 | }) 28 | } 29 | 30 | module.exports = initialize 31 | -------------------------------------------------------------------------------- /public/js/tinymce/skins/content/default/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table td,table th{border:1px solid #ccc;padding:.4rem}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 8 | -------------------------------------------------------------------------------- /public/js/tinymce/skins/content/writer/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table td,table th{border:1px solid #ccc;padding:.4rem}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 8 | -------------------------------------------------------------------------------- /public/images/icons/liked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | background 5 | 6 | 7 | 8 | Layer 1 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/images/icons/not-liked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | background 5 | 6 | 7 | 8 | Layer 1 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/js/tinymce/skins/content/document/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | @media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table td,table th{border:1px solid #ccc;padding:.4rem}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} 8 | -------------------------------------------------------------------------------- /public/js/tinymce/skins/content/dark/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body{background-color:#2f3742;color:#dfe0e4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table td,table th{border:1px solid #6d737b;padding:.4rem}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}td[data-mce-selected],th[data-mce-selected]{color:#333}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem} 8 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | 3 | const userSchema = mongoose.Schema({ 4 | firstName: { 5 | type: String, 6 | required: true 7 | }, 8 | lastName: { 9 | type: String, 10 | required: true 11 | }, 12 | email: { 13 | type: String, 14 | unique: true, 15 | required: true 16 | }, 17 | password: { 18 | type: String, 19 | required: true 20 | }, 21 | imageName: { 22 | type: String, 23 | required: true 24 | }, 25 | registrationDate: { 26 | type: String, 27 | required: true, 28 | default: Date.now 29 | }, 30 | questions: [ 31 | { 32 | type: mongoose.Schema.Types.ObjectId, 33 | ref: "Question" 34 | } 35 | ], 36 | answers: [ 37 | { 38 | type: mongoose.Schema.Types.ObjectId, 39 | ref: "Answer" 40 | } 41 | ], 42 | answersLiked: [ 43 | { 44 | type: mongoose.Schema.Types.ObjectId, 45 | ref: "Answer" 46 | } 47 | ], 48 | topics: [ 49 | { 50 | type: mongoose.Schema.Types.ObjectId, 51 | ref: "Topic" 52 | } 53 | ], 54 | topicsFollowed: [ 55 | { 56 | type: mongoose.Schema.Types.ObjectId, 57 | ref: "Topic" 58 | } 59 | ] 60 | }) 61 | 62 | module.exports = mongoose.model("User", userSchema) 63 | -------------------------------------------------------------------------------- /routes/usersRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const router = express.Router() 3 | const User = require("./../models/user") 4 | const usersController = require("./../controllers/usersController") 5 | const passport = require("passport") 6 | 7 | router.get("/", usersController.getAllUsers) 8 | router.get("/login", usersController.checkNotAuthenticated, usersController.getLoginPage) 9 | router.get("/register", usersController.checkNotAuthenticated, usersController.getRegistrationPage) 10 | router.get("/:id", usersController.getUserById) 11 | router.get("/:id/:content", usersController.getUserById) 12 | 13 | router.post( 14 | "/login", 15 | usersController.checkNotAuthenticated, 16 | passport.authenticate("local", { 17 | successRedirect: "/topics", 18 | failureRedirect: "/users/login", 19 | failureFlash: true 20 | }) 21 | ) 22 | router.post( 23 | "/register", 24 | usersController.checkNotAuthenticated, 25 | usersController.upload.single("image"), 26 | usersController.validateFormFields(), 27 | usersController.handleRegistration 28 | ) 29 | 30 | router.delete("/logout", usersController.checkAuthenticated, usersController.handleLogout) 31 | 32 | module.exports = router 33 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/code/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),o=function(o){var e=o.getContent({source_view:!0});o.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:e},onSubmit:function(e){var t,n;t=o,n=e.getData().code,t.focus(),t.undoManager.transact(function(){t.setContent(n)}),t.selection.setCursorLocation(),t.nodeChanged(),e.close()}})};!function t(){e.add("code",function(e){var t,n;return(t=e).addCommand("mceCodeEditor",function(){o(t)}),(n=e).ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:function(){return o(n)}}),n.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:function(){return o(n)}}),{}})}()}(); -------------------------------------------------------------------------------- /public/images/icons/unfollow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/js/tinymce/skins/content/default/content.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 9 | line-height: 1.4; 10 | margin: 1rem; 11 | } 12 | table { 13 | border-collapse: collapse; 14 | } 15 | table th, 16 | table td { 17 | border: 1px solid #ccc; 18 | padding: 0.4rem; 19 | } 20 | figure { 21 | display: table; 22 | margin: 1rem auto; 23 | } 24 | figure figcaption { 25 | color: #999; 26 | display: block; 27 | margin-top: 0.25rem; 28 | text-align: center; 29 | } 30 | hr { 31 | border-color: #ccc; 32 | border-style: solid; 33 | border-width: 1px 0 0 0; 34 | } 35 | code { 36 | background-color: #e8e8e8; 37 | border-radius: 3px; 38 | padding: 0.1rem 0.2rem; 39 | } 40 | .mce-content-body:not([dir=rtl]) blockquote { 41 | border-left: 2px solid #ccc; 42 | margin-left: 1.5rem; 43 | padding-left: 1rem; 44 | } 45 | .mce-content-body[dir=rtl] blockquote { 46 | border-right: 2px solid #ccc; 47 | margin-right: 1.5rem; 48 | padding-right: 1rem; 49 | } 50 | -------------------------------------------------------------------------------- /views/partials/profile-includes/user-questions.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{!-- if query string === view own quesitions --}} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{#if user.questions}} 12 | {{#each user.questions}} 13 | 14 | 21 | 22 | {{/each}} 23 | {{else}} 24 | 25 | 36 | 37 | {{/if}} 38 | 39 |
questions
15 | 16 |

{{this.content}}

17 |
18 | 19 | {{this.answers.length}} answer(s) 20 |
26 |

27 | 28 | {{#if isConnectedUserProfile}} 29 | you didnt ask any question! 30 | {{else}} 31 | 32 | {{user.firstName}} didnt ask any question! 33 | {{/if}} 34 |

35 |
-------------------------------------------------------------------------------- /public/js/tinymce/skins/content/writer/content.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 9 | line-height: 1.4; 10 | margin: 1rem auto; 11 | max-width: 900px; 12 | } 13 | table { 14 | border-collapse: collapse; 15 | } 16 | table th, 17 | table td { 18 | border: 1px solid #ccc; 19 | padding: 0.4rem; 20 | } 21 | figure { 22 | display: table; 23 | margin: 1rem auto; 24 | } 25 | figure figcaption { 26 | color: #999; 27 | display: block; 28 | margin-top: 0.25rem; 29 | text-align: center; 30 | } 31 | hr { 32 | border-color: #ccc; 33 | border-style: solid; 34 | border-width: 1px 0 0 0; 35 | } 36 | code { 37 | background-color: #e8e8e8; 38 | border-radius: 3px; 39 | padding: 0.1rem 0.2rem; 40 | } 41 | .mce-content-body:not([dir=rtl]) blockquote { 42 | border-left: 2px solid #ccc; 43 | margin-left: 1.5rem; 44 | padding-left: 1rem; 45 | } 46 | .mce-content-body[dir=rtl] blockquote { 47 | border-right: 2px solid #ccc; 48 | margin-right: 1.5rem; 49 | padding-right: 1rem; 50 | } 51 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/hr/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function () { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | var register = function (editor) { 15 | editor.addCommand('InsertHorizontalRule', function () { 16 | editor.execCommand('mceInsertContent', false, '
'); 17 | }); 18 | }; 19 | 20 | var register$1 = function (editor) { 21 | editor.ui.registry.addButton('hr', { 22 | icon: 'horizontal-rule', 23 | tooltip: 'Horizontal line', 24 | onAction: function () { 25 | return editor.execCommand('InsertHorizontalRule'); 26 | } 27 | }); 28 | editor.ui.registry.addMenuItem('hr', { 29 | icon: 'horizontal-rule', 30 | text: 'Horizontal line', 31 | onAction: function () { 32 | return editor.execCommand('InsertHorizontalRule'); 33 | } 34 | }); 35 | }; 36 | 37 | function Plugin () { 38 | global.add('hr', function (editor) { 39 | register(editor); 40 | register$1(editor); 41 | }); 42 | } 43 | 44 | Plugin(); 45 | 46 | }()); 47 | -------------------------------------------------------------------------------- /views/partials/profile-includes/user-answers.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{#if user.answers}} 10 | {{#each user.answers}} 11 | 12 | 20 | 21 | {{/each}} 22 | {{else}} 23 | 24 | 35 | 36 | {{/if}} 37 | 38 | 39 |
answers
13 | 14 |

{{this.question.content}}

15 |
16 | {{this.creationDate}} 17 | 18 |

{{{this.content}}}

19 |
25 |

26 | 27 | {{#if isConnectedUserProfile}} 28 | you didnt answer any question! 29 | {{else}} 30 | 31 | {{user.firstName}} didnt answer any question! 32 | {{/if}} 33 |

34 |
-------------------------------------------------------------------------------- /views/partials/profile-includes/user-topics-followed.hbs: -------------------------------------------------------------------------------- 1 | {{!-- else if query string === view followed topics --}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{#if user.topicsFollowed}} 10 | {{#each user.topicsFollowed}} 11 | 12 | 20 | 21 | {{/each}} 22 | {{else}} 23 | 34 | {{/if}} 35 | 36 | 37 | 38 |
topics followed
13 | 19 |
24 |

25 | 26 | {{#if isConnectedUserProfile}} 27 | you dont follow any topic! 28 | {{else}} 29 | 30 | {{user.firstName}} dont follow any topic! 31 | {{/if}} 32 |

33 |
39 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/nonbreaking/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager"),i=function(n,e){for(var a="",o=0;o'+i(" ",e)+"":i(" ",e);n.undoManager.transact(function(){return n.insertContent(o)})},c=tinymce.util.Tools.resolve("tinymce.util.VK");!function e(){n.add("nonbreaking",function(n){var e,a,o,i,t;(e=n).addCommand("mceNonBreaking",function(){r(e,1)}),(a=n).ui.registry.addButton("nonbreaking",{icon:"non-breaking",tooltip:"Nonbreaking space",onAction:function(){return a.execCommand("mceNonBreaking")}}),a.ui.registry.addMenuItem("nonbreaking",{icon:"non-breaking",text:"Nonbreaking space",onAction:function(){return a.execCommand("mceNonBreaking")}}),0<(t="boolean"==typeof(i=(o=n).getParam("nonbreaking_force_tab",0))?!0===i?3:0:i)&&o.on("keydown",function(n){if(n.keyCode===c.TAB&&!n.isDefaultPrevented()){if(n.shiftKey)return;n.preventDefault(),n.stopImmediatePropagation(),r(o,t)}})})}()}(); -------------------------------------------------------------------------------- /public/js/tinymce/skins/content/document/content.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | @media screen { 8 | html { 9 | background: #f4f4f4; 10 | min-height: 100%; 11 | } 12 | } 13 | body { 14 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 15 | } 16 | @media screen { 17 | body { 18 | background-color: #fff; 19 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.15); 20 | box-sizing: border-box; 21 | margin: 1rem auto 0; 22 | max-width: 820px; 23 | min-height: calc(100vh - 1rem); 24 | padding: 4rem 6rem 6rem 6rem; 25 | } 26 | } 27 | table { 28 | border-collapse: collapse; 29 | } 30 | table th, 31 | table td { 32 | border: 1px solid #ccc; 33 | padding: 0.4rem; 34 | } 35 | figure figcaption { 36 | color: #999; 37 | margin-top: 0.25rem; 38 | text-align: center; 39 | } 40 | hr { 41 | border-color: #ccc; 42 | border-style: solid; 43 | border-width: 1px 0 0 0; 44 | } 45 | .mce-content-body:not([dir=rtl]) blockquote { 46 | border-left: 2px solid #ccc; 47 | margin-left: 1.5rem; 48 | padding-left: 1rem; 49 | } 50 | .mce-content-body[dir=rtl] blockquote { 51 | border-right: 2px solid #ccc; 52 | margin-right: 1.5rem; 53 | padding-right: 1rem; 54 | } 55 | -------------------------------------------------------------------------------- /public/js/tinymce/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "tinymce", 3 | "_id": "tinymce@5.3.2", 4 | "_inBundle": false, 5 | "_integrity": "sha512-d8LaanlW+i/V5Ncb49gR2ncx0Tl2iWJBsepdsuT5qOlFaAPueIfc+btwwERXra7M6KDx0mOwKJaE2FBI+lHz3Q==", 6 | "_location": "/tinymce", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "tag", 10 | "registry": true, 11 | "raw": "tinymce", 12 | "name": "tinymce", 13 | "escapedName": "tinymce", 14 | "rawSpec": "", 15 | "saveSpec": null, 16 | "fetchSpec": "latest" 17 | }, 18 | "_requiredBy": [ 19 | "#USER", 20 | "/" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/tinymce/-/tinymce-5.3.2.tgz", 23 | "_shasum": "c53622edf9c4b214bb912658762d6ea831d7d469", 24 | "_spec": "tinymce", 25 | "_where": "/home/alae/work/tests/forum", 26 | "author": { 27 | "name": "Ephox Corporation" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/tinymce/tinymce/issues" 31 | }, 32 | "bundleDependencies": false, 33 | "deprecated": false, 34 | "description": "Web based JavaScript HTML WYSIWYG editor control.", 35 | "homepage": "https://github.com/tinymce/tinymce-dist#readme", 36 | "keywords": [ 37 | "editor", 38 | "wysiwyg", 39 | "tinymce", 40 | "richtext", 41 | "javascript", 42 | "html" 43 | ], 44 | "license": "LGPL-2.1", 45 | "main": "tinymce.js", 46 | "name": "tinymce", 47 | "repository": { 48 | "type": "git", 49 | "url": "git+https://github.com/tinymce/tinymce-dist.git" 50 | }, 51 | "version": "5.3.2" 52 | } 53 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/visualblocks/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var o=tinymce.util.Tools.resolve("tinymce.PluginManager"),r=function(o,t,e){var n,s;o.dom.toggleClass(o.getBody(),"mce-visualblocks"),e.set(!e.get()),n=o,s=e.get(),n.fire("VisualBlocks",{state:s})},m=function(e,n){return function(t){t.setActive(n.get());var o=function(o){return t.setActive(o.state)};return e.on("VisualBlocks",o),function(){return e.off("VisualBlocks",o)}}};!function t(){o.add("visualblocks",function(o,t){var e,n,s,i,c,u,l,a=(e=!1,{get:function(){return e},set:function(o){e=o}});s=a,(n=o).addCommand("mceVisualBlocks",function(){r(n,0,s)}),c=a,(i=o).ui.registry.addToggleButton("visualblocks",{icon:"visualblocks",tooltip:"Show blocks",onAction:function(){return i.execCommand("mceVisualBlocks")},onSetup:m(i,c)}),i.ui.registry.addToggleMenuItem("visualblocks",{text:"Show blocks",icon:"visualblocks",onAction:function(){return i.execCommand("mceVisualBlocks")},onSetup:m(i,c)}),l=a,(u=o).on("PreviewFormats AfterPreviewFormats",function(o){l.get()&&u.dom.toggleClass(u.getBody(),"mce-visualblocks","afterpreviewformats"===o.type)}),u.on("init",function(){u.getParam("visualblocks_default_state",!1,"boolean")&&r(u,0,l)}),u.on("remove",function(){u.dom.removeClass(u.getBody(),"mce-visualblocks")})})}()}(); -------------------------------------------------------------------------------- /views/partials/profile-includes/user-topics-created.hbs: -------------------------------------------------------------------------------- 1 | {{!-- else if query string === view own topics --}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{#if user.topics}} 10 | {{#each user.topics}} 11 | 12 | 20 | 21 | {{/each}} 22 | 23 | {{else}} 24 | 25 | 36 | 37 | {{/if}} 38 | 39 | 40 |
topics created
13 | 19 |
26 |

27 | 28 | {{#if isConnectedUserProfile}} 29 | you didnt create any topic! 30 | {{else}} 31 | 32 | {{user.firstName}} didnt create any topic! 33 | {{/if}} 34 |

35 |
-------------------------------------------------------------------------------- /public/images/icons/follow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/js/tinymce/skins/content/dark/content.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body { 8 | background-color: #2f3742; 9 | color: #dfe0e4; 10 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 11 | line-height: 1.4; 12 | margin: 1rem; 13 | } 14 | a { 15 | color: #4099ff; 16 | } 17 | table { 18 | border-collapse: collapse; 19 | } 20 | table th, 21 | table td { 22 | border: 1px solid #6d737b; 23 | padding: 0.4rem; 24 | } 25 | figure { 26 | display: table; 27 | margin: 1rem auto; 28 | } 29 | figure figcaption { 30 | color: #8a8f97; 31 | display: block; 32 | margin-top: 0.25rem; 33 | text-align: center; 34 | } 35 | hr { 36 | border-color: #6d737b; 37 | border-style: solid; 38 | border-width: 1px 0 0 0; 39 | } 40 | code { 41 | background-color: #6d737b; 42 | border-radius: 3px; 43 | padding: 0.1rem 0.2rem; 44 | } 45 | /* Make text in selected cells in tables dark and readable */ 46 | td[data-mce-selected], 47 | th[data-mce-selected] { 48 | color: #333; 49 | } 50 | .mce-content-body:not([dir=rtl]) blockquote { 51 | border-left: 2px solid #6d737b; 52 | margin-left: 1.5rem; 53 | padding-left: 1rem; 54 | } 55 | .mce-content-body[dir=rtl] blockquote { 56 | border-right: 2px solid #6d737b; 57 | margin-right: 1.5rem; 58 | padding-right: 1rem; 59 | } 60 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/print/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function () { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | var global$1 = tinymce.util.Tools.resolve('tinymce.Env'); 15 | 16 | var register = function (editor) { 17 | editor.addCommand('mcePrint', function () { 18 | if (global$1.browser.isIE()) { 19 | editor.getDoc().execCommand('print', false, null); 20 | } else { 21 | editor.getWin().print(); 22 | } 23 | }); 24 | }; 25 | 26 | var register$1 = function (editor) { 27 | editor.ui.registry.addButton('print', { 28 | icon: 'print', 29 | tooltip: 'Print', 30 | onAction: function () { 31 | return editor.execCommand('mcePrint'); 32 | } 33 | }); 34 | editor.ui.registry.addMenuItem('print', { 35 | text: 'Print...', 36 | icon: 'print', 37 | onAction: function () { 38 | return editor.execCommand('mcePrint'); 39 | } 40 | }); 41 | }; 42 | 43 | function Plugin () { 44 | global.add('print', function (editor) { 45 | register(editor); 46 | register$1(editor); 47 | editor.addShortcut('Meta+P', '', 'mcePrint'); 48 | }); 49 | } 50 | 51 | Plugin(); 52 | 53 | }()); 54 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/pagebreak/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env"),c=function(){return"mce-pagebreak"},g=function(){return''};!function n(){e.add("pagebreak",function(e){var a,n,o,i,t,r;(a=e).addCommand("mcePageBreak",function(){a.settings.pagebreak_split_block?a.insertContent("

"+g()+"

"):a.insertContent(g())}),(n=e).ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:function(){return n.execCommand("mcePageBreak")}}),n.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:function(){return n.execCommand("mcePageBreak")}}),i=(o=e).getParam("pagebreak_separator","\x3c!-- pagebreak --\x3e"),t=new RegExp(i.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(e){return"\\"+e}),"gi"),o.on("BeforeSetContent",function(e){e.content=e.content.replace(t,g())}),o.on("PreInit",function(){o.serializer.addNodeFilter("img",function(e){for(var a,n,t=e.length;t--;)if((n=(a=e[t]).attr("class"))&&-1!==n.indexOf("mce-pagebreak")){var r=a.parent;if(o.schema.getBlockElements()[r.name]&&o.getParam("pagebreak_split_block",!1)){r.type=3,r.value=i,r.raw=!0,a.remove();continue}a.type=3,a.value=i,a.raw=!0}})}),(r=e).on("ResolveName",function(e){"IMG"===e.target.nodeName&&r.dom.hasClass(e.target,c())&&(e.name="pagebreak")})})}()}(); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/save/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),o=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),a=tinymce.util.Tools.resolve("tinymce.util.Tools"),i=function(e){return e.getParam("save_enablewhendirty",!0)},c=function(e,n){e.notificationManager.open({text:n,type:"error"})},t=function(t){t.addCommand("mceSave",function(){!function(e){var n;if(n=o.DOM.getParent(e.id,"form"),!i(e)||e.isDirty()){if(e.save(),e.getParam("save_onsavecallback"))return e.execCallback("save_onsavecallback",e),e.nodeChanged();n?(e.setDirty(!1),n.onsubmit&&!n.onsubmit()||("function"==typeof n.submit?n.submit():c(e,"Error: Form submit field collision.")),e.nodeChanged()):c(e,"Error: No form element found.")}}(t)}),t.addCommand("mceCancel",function(){var e,n;e=t,n=a.trim(e.startContent),e.getParam("save_oncancelcallback")?e.execCallback("save_oncancelcallback",e):e.resetContent(n)})},r=function(t){return function(e){var n=function(){e.setDisabled(i(t)&&!t.isDirty())};return t.on("NodeChange dirty",n),function(){return t.off("NodeChange dirty",n)}}};!function n(){e.add("save",function(e){var n;(n=e).ui.registry.addButton("save",{icon:"save",tooltip:"Save",disabled:!0,onAction:function(){return n.execCommand("mceSave")},onSetup:r(n)}),n.ui.registry.addButton("cancel",{icon:"cancel",tooltip:"Cancel",disabled:!0,onAction:function(){return n.execCommand("mceCancel")},onSetup:r(n)}),n.addShortcut("Meta+S","","mceSave"),t(e)})}()}(); -------------------------------------------------------------------------------- /routes/topicsRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const router = express.Router() 3 | const topicsController = require("./../controllers/topicsController") 4 | const usersController = require("./../controllers/usersController") 5 | const Topic = require("../models/topic") 6 | 7 | router.get("/", topicsController.getAllTopics) 8 | 9 | router.get("/create", usersController.checkAuthenticated, topicsController.getCreateTopicPage) 10 | router.post( 11 | "/create", 12 | usersController.checkAuthenticated, 13 | topicsController.upload.single("image"), 14 | topicsController.validateTopicFormFields(), 15 | topicsController.handleCreateTopic 16 | ) 17 | 18 | router.get("/:id", topicsController.getTopicById) 19 | 20 | router.get("/:id/question", usersController.checkAuthenticated, topicsController.getAskQuestionPage) 21 | router.post("/:id/question", usersController.checkAuthenticated, topicsController.validateQuestionFormFields(), topicsController.handleCreateQuestion) 22 | 23 | router.post("/follow-unfollow", usersController.checkAuthenticated, topicsController.handleFollowingUnfollowing) 24 | 25 | /* 26 | these two routes are for when we want to search a topic by name 27 | in the js script in the topics.hbs file i have a form, and when the user enters something, i will make a GET request to 28 | /topics/api/search/entered-value 29 | 30 | so there is two scenarios: 31 | if the text entered by the user is blank (empty string), a GET request will be made to: /topics/api/search/ 32 | if the text entered by the user is not blank, a GET request will be made to: /topics/api/search/entered-value 33 | */ 34 | router.get("/api/search", topicsController.handleTopicSearch1) 35 | router.get("/api/search/:searchInput", topicsController.handleTopicSearch2) 36 | 37 | module.exports = router 38 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/noneditable/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),l=tinymce.util.Tools.resolve("tinymce.util.Tools"),u=function(t){return t.getParam("noneditable_noneditable_class","mceNonEditable")},f=function(n){return function(t){return-1!==(" "+t.attr("class")+" ").indexOf(n)}},s=function(i,o,c){return function(t){var n=arguments,e=n[n.length-2],r=0"===r){var a=o.lastIndexOf("<",e);if(-1!==a)if(-1!==o.substring(a,e).indexOf('contenteditable="false"'))return t}return''+i.dom.encode("string"==typeof n[1]?n[1]:n[0])+""}},n=function(n){var t,e,r="contenteditable";t=" "+l.trim(n.getParam("noneditable_editable_class","mceEditable"))+" ",e=" "+l.trim(u(n))+" ";var a,i=f(t),o=f(e),c=(a=n.getParam("noneditable_regexp",[]))&&a.constructor===RegExp?[a]:a;n.on("PreInit",function(){0 { 6 | try { 7 | const userId = req.user._id 8 | const { answerId } = req.body 9 | 10 | console.log(userId) 11 | console.log(answerId) 12 | 13 | const answer = await Answer.findById(answerId) 14 | const user = await User.findById(userId) 15 | 16 | if (user && answer) { 17 | const isLiking = answer.usersWhoLike.includes(userId) && user.answersLiked.includes(answerId) 18 | 19 | let updatedUser = null 20 | let updatedAnswer = null 21 | 22 | if (isLiking) { 23 | //unlike 24 | updatedUser = await User.findByIdAndUpdate( 25 | userId, 26 | { 27 | $pull: { answersLiked: answerId } 28 | }, 29 | { 30 | new: true, 31 | useFindAndModify: false 32 | } 33 | ) 34 | 35 | updatedAnswer = await Answer.findByIdAndUpdate( 36 | answerId, 37 | { 38 | $pull: { usersWhoLike: userId } 39 | }, 40 | { 41 | new: true, 42 | useFindAndModify: false 43 | } 44 | ) 45 | } else { 46 | updatedUser = await User.findByIdAndUpdate( 47 | userId, 48 | { 49 | $push: { answersLiked: answerId } 50 | }, 51 | { 52 | new: true, 53 | useFindAndModify: false 54 | } 55 | ) 56 | 57 | updatedAnswer = await Answer.findByIdAndUpdate( 58 | answerId, 59 | { 60 | $push: { usersWhoLike: userId } 61 | }, 62 | { 63 | new: true, 64 | useFindAndModify: false 65 | } 66 | ) 67 | } 68 | 69 | res.json({ isLiking: !isLiking, answer: updatedAnswer }) 70 | } 71 | } catch (error) { 72 | next(error) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/tabfocus/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(s){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),a=tinymce.util.Tools.resolve("tinymce.EditorManager"),y=tinymce.util.Tools.resolve("tinymce.Env"),f=tinymce.util.Tools.resolve("tinymce.util.Delay"),d=tinymce.util.Tools.resolve("tinymce.util.Tools"),m=tinymce.util.Tools.resolve("tinymce.util.VK"),v=t.DOM,n=function(e){e.keyCode!==m.TAB||e.ctrlKey||e.altKey||e.metaKey||e.preventDefault()},i=function(c){function e(n){var i,o,e,l,t;if(!(n.keyCode!==m.TAB||n.ctrlKey||n.altKey||n.metaKey||n.isDefaultPrevented())&&(1===(e=d.explode((t=c).getParam("tab_focus",t.getParam("tabfocus_elements",":prev,:next")))).length&&(e[1]=e[0],e[0]=":prev"),o=n.shiftKey?":prev"===e[0]?r(-1):v.get(e[0]):":next"===e[1]?r(1):v.get(e[1]))){var u=a.get(o.id||o.name);o.id&&u?u.focus():f.setTimeout(function(){y.webkit||s.window.focus(),o.focus()},10),n.preventDefault()}function r(e){function t(e){return/INPUT|TEXTAREA|BUTTON/.test(e.tagName)&&a.get(n.id)&&-1!==e.tabIndex&&function t(e){return"BODY"===e.nodeName||"hidden"!==e.type&&"none"!==e.style.display&&"hidden"!==e.style.visibility&&t(e.parentNode)}(e)}if(o=v.select(":input:enabled,*[tabindex]:not(iframe)"),d.each(o,function(e,t){if(e.id===c.id)return i=t,!1}),0 2 |
3 |
4 |
5 | 6 | 9 | 10 | 11 | 12 |
13 | 14 | {{#if messages.error}} 15 |
16 | {{messages.error}} 17 |
18 | {{/if}} 19 | 20 |
21 |
22 | 23 |
24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 | 33 |

34 |
35 | 36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /views/write-answer.hbs: -------------------------------------------------------------------------------- 1 | 2 | 7 |
8 |
9 |
10 | 11 | {{#if question}} 12 |
13 | 14 |

{{question.content}}

15 | 16 | {{#if successMessage}} 17 |
18 | {{successMessage}} 19 |
20 | {{/if}} 21 | 22 |
23 | 24 | 25 |
26 | 27 | 35 | 36 |
37 | {{else}} 38 |
39 |

question does not exist!

40 |
41 | 42 | {{/if}} 43 | 44 | 45 | 46 |
47 |
48 |
-------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('test:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | node-server: 4 | container_name: node-server 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | networks: 9 | - $NETWORK_NAME 10 | environment: 11 | - DB_HOST=$MONGODB_SERVER 12 | - DB_USER=$MONGODB_USER 13 | - DB_PASSWORD=$MONGODB_PASSWORD 14 | - DB_NAME=$MONGODB_DATABASE 15 | - DB_PORT=$MONGODB_DOCKER_PORT 16 | restart: unless-stopped 17 | ports: 18 | - $NODE_HOST_PORT:$NODE_DOCKER_PORT 19 | links: 20 | - $MONGODB_SERVER 21 | depends_on: 22 | - $MONGODB_SERVER 23 | mongodb: 24 | container_name: mongodb 25 | image: mongo:4.2.23 26 | networks: 27 | - $NETWORK_NAME 28 | restart: unless-stopped 29 | ports: 30 | - $MONGODB_HOST_PORT:$MONGODB_DOCKER_PORT 31 | environment: 32 | - MONGO_INITDB_DATABASE=$MONGODB_DATABASE 33 | - MONGO_INITDB_ROOT_USERNAME=$MONGODB_USER 34 | - MONGO_INITDB_ROOT_PASSWORD=$MONGODB_PASSWORD 35 | volumes: 36 | - ./mongodb-database/:/docker-entrypoint-initdb.d/ 37 | mongo-express: 38 | container_name: mongo-express 39 | image: mongo-express:0.54.0 40 | networks: 41 | - $NETWORK_NAME 42 | restart: unless-stopped 43 | ports: 44 | - $MONGO_EXPRESS_HOST_PORT:$MONGO_EXPRESS_DOCKER_PORT 45 | environment: 46 | - ME_CONFIG_MONGODB_SERVER=$MONGODB_SERVER 47 | - ME_CONFIG_MONGODB_PORT=$MONGODB_DOCKER_PORT 48 | - ME_CONFIG_MONGODB_ADMINUSERNAME=$MONGODB_USER 49 | - ME_CONFIG_MONGODB_ADMINPASSWORD=$MONGODB_PASSWORD 50 | depends_on: 51 | - $MONGODB_SERVER 52 | 53 | networks: 54 | node_mongo_mongoexpress_network: 55 | name: $NETWORK_NAME 56 | driver: bridge -------------------------------------------------------------------------------- /views/ask-question.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | {{#if topic}} 6 |
7 |

{{topic.name}}

8 | 9 | {{#if successMessage}} 10 |
11 | {{successMessage}} 12 |
13 | {{/if}} 14 | 15 | 16 | {{#if errors}} 17 | {{#each errors}} 18 |
19 | {{this.msg}} 20 |
21 | {{/each}} 22 | {{/if}} 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
31 | 32 | 40 | 41 |
42 | {{else}} 43 |
44 |

topic does not exist!

45 |
46 | {{/if}} 47 |
48 |
49 |
-------------------------------------------------------------------------------- /public/js/tinymce/plugins/anchor/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=function(e,t){return"A"===t.tagName&&""===e.dom.getAttrib(t,"href")},r=function(e,t){return/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(o=t,r=(n=e).selection.getNode(),a(n,r)?(r.removeAttribute("name"),r.id=o,n.undoManager.add()):(n.focus(),n.selection.collapse(!0),n.insertContent(n.dom.createHTML("a",{id:o}))),!0):(e.windowManager.alert("Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1);var n,o,r},i=function(t){var e,n,o=(n=(e=t).selection.getNode(),a(e,n)?n.getAttribute("id")||n.getAttribute("name"):"");t.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:o},onSubmit:function(e){r(t,e.getData().id)&&e.close()}})},c=function(o){return function(e){for(var t=0;t',e&&(n+='");var o=t.getParam("content_css_cors",!1,"boolean")?' crossorigin="anonymous"':"";l.each(t.contentCSS,function(e){n+='"});var r=t.settings.body_id||"tinymce";-1!==r.indexOf("=")&&(r=(r=t.getParam("body_id","","hash"))[t.id]||r);var a=t.settings.body_class||"";-1!==a.indexOf("=")&&(a=(a=t.getParam("body_class","","hash"))[t.id]||"");var c=' '; 49 | var directionality = editor.getBody().dir; 50 | var dirAttr = directionality ? ' dir="' + encode(directionality) + '"' : ''; 51 | var previewHtml = '' + '' + '' + headHtml + '' + '' + editor.getContent() + preventClicksOnLinksScript + '' + ''; 52 | return previewHtml; 53 | }; 54 | 55 | var open = function (editor) { 56 | var content = getPreviewHtml(editor); 57 | var dataApi = editor.windowManager.open({ 58 | title: 'Preview', 59 | size: 'large', 60 | body: { 61 | type: 'panel', 62 | items: [{ 63 | name: 'preview', 64 | type: 'iframe', 65 | sandboxed: true 66 | }] 67 | }, 68 | buttons: [{ 69 | type: 'cancel', 70 | name: 'close', 71 | text: 'Close', 72 | primary: true 73 | }], 74 | initialData: { preview: content } 75 | }); 76 | dataApi.focus('close'); 77 | }; 78 | 79 | var register = function (editor) { 80 | editor.addCommand('mcePreview', function () { 81 | open(editor); 82 | }); 83 | }; 84 | 85 | var register$1 = function (editor) { 86 | editor.ui.registry.addButton('preview', { 87 | icon: 'preview', 88 | tooltip: 'Preview', 89 | onAction: function () { 90 | return editor.execCommand('mcePreview'); 91 | } 92 | }); 93 | editor.ui.registry.addMenuItem('preview', { 94 | icon: 'preview', 95 | text: 'Preview', 96 | onAction: function () { 97 | return editor.execCommand('mcePreview'); 98 | } 99 | }); 100 | }; 101 | 102 | function Plugin () { 103 | global.add('preview', function (editor) { 104 | register(editor); 105 | register$1(editor); 106 | }); 107 | } 108 | 109 | Plugin(); 110 | 111 | }()); 112 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/bbcode/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function () { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools'); 15 | 16 | var html2bbcode = function (s) { 17 | s = global$1.trim(s); 18 | var rep = function (re, str) { 19 | s = s.replace(re, str); 20 | }; 21 | rep(/(.*?)<\/a>/gi, '[url=$1]$2[/url]'); 22 | rep(/(.*?)<\/font>/gi, '[code][color=$1]$2[/color][/code]'); 23 | rep(/(.*?)<\/font>/gi, '[quote][color=$1]$2[/color][/quote]'); 24 | rep(/(.*?)<\/font>/gi, '[code][color=$1]$2[/color][/code]'); 25 | rep(/(.*?)<\/font>/gi, '[quote][color=$1]$2[/color][/quote]'); 26 | rep(/(.*?)<\/span>/gi, '[color=$1]$2[/color]'); 27 | rep(/(.*?)<\/font>/gi, '[color=$1]$2[/color]'); 28 | rep(/(.*?)<\/span>/gi, '[size=$1]$2[/size]'); 29 | rep(/(.*?)<\/font>/gi, '$1'); 30 | rep(//gi, '[img]$1[/img]'); 31 | rep(/(.*?)<\/span>/gi, '[code]$1[/code]'); 32 | rep(/(.*?)<\/span>/gi, '[quote]$1[/quote]'); 33 | rep(/(.*?)<\/strong>/gi, '[code][b]$1[/b][/code]'); 34 | rep(/(.*?)<\/strong>/gi, '[quote][b]$1[/b][/quote]'); 35 | rep(/(.*?)<\/em>/gi, '[code][i]$1[/i][/code]'); 36 | rep(/(.*?)<\/em>/gi, '[quote][i]$1[/i][/quote]'); 37 | rep(/(.*?)<\/u>/gi, '[code][u]$1[/u][/code]'); 38 | rep(/(.*?)<\/u>/gi, '[quote][u]$1[/u][/quote]'); 39 | rep(/<\/(strong|b)>/gi, '[/b]'); 40 | rep(/<(strong|b)>/gi, '[b]'); 41 | rep(/<\/(em|i)>/gi, '[/i]'); 42 | rep(/<(em|i)>/gi, '[i]'); 43 | rep(/<\/u>/gi, '[/u]'); 44 | rep(/(.*?)<\/span>/gi, '[u]$1[/u]'); 45 | rep(//gi, '[u]'); 46 | rep(/]*>/gi, '[quote]'); 47 | rep(/<\/blockquote>/gi, '[/quote]'); 48 | rep(/
/gi, '\n'); 49 | rep(//gi, '\n'); 50 | rep(/
/gi, '\n'); 51 | rep(/

/gi, ''); 52 | rep(/<\/p>/gi, '\n'); 53 | rep(/ |\u00a0/gi, ' '); 54 | rep(/"/gi, '"'); 55 | rep(/</gi, '<'); 56 | rep(/>/gi, '>'); 57 | rep(/&/gi, '&'); 58 | return s; 59 | }; 60 | var bbcode2html = function (s) { 61 | s = global$1.trim(s); 62 | var rep = function (re, str) { 63 | s = s.replace(re, str); 64 | }; 65 | rep(/\n/gi, '
'); 66 | rep(/\[b\]/gi, ''); 67 | rep(/\[\/b\]/gi, ''); 68 | rep(/\[i\]/gi, ''); 69 | rep(/\[\/i\]/gi, ''); 70 | rep(/\[u\]/gi, ''); 71 | rep(/\[\/u\]/gi, ''); 72 | rep(/\[url=([^\]]+)\](.*?)\[\/url\]/gi, '$2'); 73 | rep(/\[url\](.*?)\[\/url\]/gi, '$1'); 74 | rep(/\[img\](.*?)\[\/img\]/gi, ''); 75 | rep(/\[color=(.*?)\](.*?)\[\/color\]/gi, '$2'); 76 | rep(/\[code\](.*?)\[\/code\]/gi, '$1 '); 77 | rep(/\[quote.*?\](.*?)\[\/quote\]/gi, '$1 '); 78 | return s; 79 | }; 80 | 81 | function Plugin () { 82 | global.add('bbcode', function (editor) { 83 | editor.on('BeforeSetContent', function (e) { 84 | e.content = bbcode2html(e.content); 85 | }); 86 | editor.on('PostProcess', function (e) { 87 | if (e.set) { 88 | e.content = bbcode2html(e.content); 89 | } 90 | if (e.get) { 91 | e.content = html2bbcode(e.content); 92 | } 93 | }); 94 | }); 95 | } 96 | 97 | Plugin(); 98 | 99 | }()); 100 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/anchor/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function () { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | var isNamedAnchor = function (editor, node) { 15 | return node.tagName === 'A' && editor.dom.getAttrib(node, 'href') === ''; 16 | }; 17 | var isValidId = function (id) { 18 | return /^[A-Za-z][A-Za-z0-9\-:._]*$/.test(id); 19 | }; 20 | var getId = function (editor) { 21 | var selectedNode = editor.selection.getNode(); 22 | return isNamedAnchor(editor, selectedNode) ? selectedNode.getAttribute('id') || selectedNode.getAttribute('name') : ''; 23 | }; 24 | var insert = function (editor, id) { 25 | var selectedNode = editor.selection.getNode(); 26 | if (isNamedAnchor(editor, selectedNode)) { 27 | selectedNode.removeAttribute('name'); 28 | selectedNode.id = id; 29 | editor.undoManager.add(); 30 | } else { 31 | editor.focus(); 32 | editor.selection.collapse(true); 33 | editor.insertContent(editor.dom.createHTML('a', { id: id })); 34 | } 35 | }; 36 | 37 | var insertAnchor = function (editor, newId) { 38 | if (!isValidId(newId)) { 39 | editor.windowManager.alert('Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.'); 40 | return false; 41 | } else { 42 | insert(editor, newId); 43 | return true; 44 | } 45 | }; 46 | var open = function (editor) { 47 | var currentId = getId(editor); 48 | editor.windowManager.open({ 49 | title: 'Anchor', 50 | size: 'normal', 51 | body: { 52 | type: 'panel', 53 | items: [{ 54 | name: 'id', 55 | type: 'input', 56 | label: 'ID', 57 | placeholder: 'example' 58 | }] 59 | }, 60 | buttons: [ 61 | { 62 | type: 'cancel', 63 | name: 'cancel', 64 | text: 'Cancel' 65 | }, 66 | { 67 | type: 'submit', 68 | name: 'save', 69 | text: 'Save', 70 | primary: true 71 | } 72 | ], 73 | initialData: { id: currentId }, 74 | onSubmit: function (api) { 75 | if (insertAnchor(editor, api.getData().id)) { 76 | api.close(); 77 | } 78 | } 79 | }); 80 | }; 81 | 82 | var register = function (editor) { 83 | editor.addCommand('mceAnchor', function () { 84 | open(editor); 85 | }); 86 | }; 87 | 88 | var isNamedAnchorNode = function (node) { 89 | return !node.attr('href') && (node.attr('id') || node.attr('name')) && !node.firstChild; 90 | }; 91 | var setContentEditable = function (state) { 92 | return function (nodes) { 93 | for (var i = 0; i < nodes.length; i++) { 94 | if (isNamedAnchorNode(nodes[i])) { 95 | nodes[i].attr('contenteditable', state); 96 | } 97 | } 98 | }; 99 | }; 100 | var setup = function (editor) { 101 | editor.on('PreInit', function () { 102 | editor.parser.addNodeFilter('a', setContentEditable('false')); 103 | editor.serializer.addNodeFilter('a', setContentEditable(null)); 104 | }); 105 | }; 106 | 107 | var register$1 = function (editor) { 108 | editor.ui.registry.addToggleButton('anchor', { 109 | icon: 'bookmark', 110 | tooltip: 'Anchor', 111 | onAction: function () { 112 | return editor.execCommand('mceAnchor'); 113 | }, 114 | onSetup: function (buttonApi) { 115 | return editor.selection.selectorChangedWithUnbind('a:not([href])', buttonApi.setActive).unbind; 116 | } 117 | }); 118 | editor.ui.registry.addMenuItem('anchor', { 119 | icon: 'bookmark', 120 | text: 'Anchor...', 121 | onAction: function () { 122 | return editor.execCommand('mceAnchor'); 123 | } 124 | }); 125 | }; 126 | 127 | function Plugin () { 128 | global.add('anchor', function (editor) { 129 | setup(editor); 130 | register(editor); 131 | register$1(editor); 132 | }); 133 | } 134 | 135 | Plugin(); 136 | 137 | }()); 138 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/noneditable/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function () { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools'); 15 | 16 | var getNonEditableClass = function (editor) { 17 | return editor.getParam('noneditable_noneditable_class', 'mceNonEditable'); 18 | }; 19 | var getEditableClass = function (editor) { 20 | return editor.getParam('noneditable_editable_class', 'mceEditable'); 21 | }; 22 | var getNonEditableRegExps = function (editor) { 23 | var nonEditableRegExps = editor.getParam('noneditable_regexp', []); 24 | if (nonEditableRegExps && nonEditableRegExps.constructor === RegExp) { 25 | return [nonEditableRegExps]; 26 | } else { 27 | return nonEditableRegExps; 28 | } 29 | }; 30 | 31 | var hasClass = function (checkClassName) { 32 | return function (node) { 33 | return (' ' + node.attr('class') + ' ').indexOf(checkClassName) !== -1; 34 | }; 35 | }; 36 | var replaceMatchWithSpan = function (editor, content, cls) { 37 | return function (match) { 38 | var args = arguments, index = args[args.length - 2]; 39 | var prevChar = index > 0 ? content.charAt(index - 1) : ''; 40 | if (prevChar === '"') { 41 | return match; 42 | } 43 | if (prevChar === '>') { 44 | var findStartTagIndex = content.lastIndexOf('<', index); 45 | if (findStartTagIndex !== -1) { 46 | var tagHtml = content.substring(findStartTagIndex, index); 47 | if (tagHtml.indexOf('contenteditable="false"') !== -1) { 48 | return match; 49 | } 50 | } 51 | } 52 | return '' + editor.dom.encode(typeof args[1] === 'string' ? args[1] : args[0]) + ''; 53 | }; 54 | }; 55 | var convertRegExpsToNonEditable = function (editor, nonEditableRegExps, e) { 56 | var i = nonEditableRegExps.length, content = e.content; 57 | if (e.format === 'raw') { 58 | return; 59 | } 60 | while (i--) { 61 | content = content.replace(nonEditableRegExps[i], replaceMatchWithSpan(editor, content, getNonEditableClass(editor))); 62 | } 63 | e.content = content; 64 | }; 65 | var setup = function (editor) { 66 | var editClass, nonEditClass; 67 | var contentEditableAttrName = 'contenteditable'; 68 | editClass = ' ' + global$1.trim(getEditableClass(editor)) + ' '; 69 | nonEditClass = ' ' + global$1.trim(getNonEditableClass(editor)) + ' '; 70 | var hasEditClass = hasClass(editClass); 71 | var hasNonEditClass = hasClass(nonEditClass); 72 | var nonEditableRegExps = getNonEditableRegExps(editor); 73 | editor.on('PreInit', function () { 74 | if (nonEditableRegExps.length > 0) { 75 | editor.on('BeforeSetContent', function (e) { 76 | convertRegExpsToNonEditable(editor, nonEditableRegExps, e); 77 | }); 78 | } 79 | editor.parser.addAttributeFilter('class', function (nodes) { 80 | var i = nodes.length, node; 81 | while (i--) { 82 | node = nodes[i]; 83 | if (hasEditClass(node)) { 84 | node.attr(contentEditableAttrName, 'true'); 85 | } else if (hasNonEditClass(node)) { 86 | node.attr(contentEditableAttrName, 'false'); 87 | } 88 | } 89 | }); 90 | editor.serializer.addAttributeFilter(contentEditableAttrName, function (nodes) { 91 | var i = nodes.length, node; 92 | while (i--) { 93 | node = nodes[i]; 94 | if (!hasEditClass(node) && !hasNonEditClass(node)) { 95 | continue; 96 | } 97 | if (nonEditableRegExps.length > 0 && node.attr('data-mce-content')) { 98 | node.name = '#text'; 99 | node.type = 3; 100 | node.raw = true; 101 | node.value = node.attr('data-mce-content'); 102 | } else { 103 | node.attr(contentEditableAttrName, null); 104 | } 105 | } 106 | }); 107 | }); 108 | }; 109 | 110 | function Plugin () { 111 | global.add('noneditable', function (editor) { 112 | setup(editor); 113 | }); 114 | } 115 | 116 | Plugin(); 117 | 118 | }()); 119 | -------------------------------------------------------------------------------- /controllers/usersController.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | const User = require("./../models/user") 3 | const passport = require("passport") 4 | const multer = require("multer") 5 | const path = require("path") 6 | const bcrypt = require("bcrypt") 7 | const validator = require("express-validator") 8 | 9 | //profile photo upload setup 10 | const storage = multer.diskStorage({ 11 | destination: function (req, file, cb) { 12 | cb(null, path.join(__dirname, "..", "public", "images", "users-images")) 13 | }, 14 | filename: function (req, file, cb) { 15 | cb(null, file.fieldname + "-" + Date.now() + path.extname(file.originalname)) 16 | } 17 | }) 18 | exports.upload = multer({ storage: storage }) 19 | 20 | //util functions for passport 21 | exports.findUserByEmail = async (email) => { 22 | try { 23 | const user = await User.findOne({ email: email }) 24 | return user 25 | } catch (error) { 26 | next(error) 27 | } 28 | } 29 | 30 | exports.findUserById = async (id) => { 31 | try { 32 | const user = await User.findById(id) 33 | return user 34 | } catch (error) { 35 | next(error) 36 | } 37 | } 38 | 39 | exports.checkAuthenticated = (req, res, next) => { 40 | if (req.isAuthenticated()) { 41 | next() 42 | } else { 43 | res.redirect("/users/login") 44 | } 45 | } 46 | 47 | exports.checkNotAuthenticated = (req, res, next) => { 48 | if (req.isAuthenticated()) { 49 | res.redirect("/users") 50 | } else { 51 | next() 52 | } 53 | } 54 | 55 | //handling routes functions 56 | exports.getAllUsers = async (req, res, next) => { 57 | try { 58 | const users = await User.find() 59 | res.json(users) 60 | } catch (error) { 61 | next(error) 62 | } 63 | } 64 | 65 | exports.getUserById = async (req, res, next) => { 66 | //the object that will be rendered 67 | const input = { 68 | connectedUser: req.user, 69 | title: "profile page" 70 | } 71 | 72 | try { 73 | const id = req.params.id 74 | const user = await User.findById(id) 75 | .populate("questions") 76 | .populate({ 77 | path: "answers", 78 | populate: { path: "question" } 79 | }) 80 | .populate("topics") 81 | .populate("topicsFollowed") 82 | 83 | if (user) { 84 | input.title = user.firstName + " " + user.lastName 85 | user.registrationDate = new Date(parseInt(user.registrationDate)).toDateString() 86 | input.user = user 87 | 88 | if (input.connectedUser) { 89 | input.isConnectedUserProfile = input.connectedUser._id.toString() === user._id.toString() 90 | } 91 | 92 | //if we get to this method using this route: /users/:id/:content and not /users/:id 93 | if (req.params.content) { 94 | const { content } = req.params 95 | if (content === "questions") { 96 | input.questions = true 97 | } else if (content === "answers") { 98 | input.answers = true 99 | } else if (content === "topics") { 100 | input.topics = true 101 | } else if (content === "topics-followed") { 102 | input.topicsFollowed = true 103 | } 104 | } 105 | } 106 | res.render("profile", input) 107 | } catch (error) { 108 | if (error.message && error.message.includes("Cast to ObjectId failed for value")) { 109 | res.render("profile", input) 110 | } else { 111 | next(error) 112 | } 113 | } 114 | } 115 | 116 | exports.getLoginPage = (req, res, next) => { 117 | const input = { 118 | title: "login" 119 | } 120 | res.render("login", input) 121 | } 122 | 123 | exports.getRegistrationPage = (req, res, next) => { 124 | const input = { 125 | title: "register" 126 | } 127 | res.render("register", input) 128 | } 129 | 130 | exports.validateFormFields = () => { 131 | return [ 132 | validator 133 | .check("firstName") 134 | .notEmpty() 135 | .withMessage("firstName cannot be empty!") 136 | .isLength({ min: 2 }) 137 | .withMessage("firstName length must be at least 2 chars!"), 138 | 139 | validator 140 | .check("lastName") 141 | .notEmpty() 142 | .withMessage("lastName cannot be empty!") 143 | .isLength({ min: 2 }) 144 | .withMessage("lastName length must be at least 2 chars!"), 145 | 146 | validator.check("email").isEmail().withMessage("the email is not valid!"), 147 | 148 | validator.check("password").isLength({ min: 4 }).withMessage("password lenght must be at least 4 chars!") 149 | ] 150 | } 151 | 152 | exports.handleRegistration = async (req, res, next) => { 153 | const input = { 154 | title: "register" 155 | } 156 | try { 157 | const errors = validator.validationResult(req) 158 | 159 | if (!errors.isEmpty()) { 160 | res.render("register", { errors: errors.array() }) 161 | } else { 162 | const { firstName, lastName, email, password } = req.body 163 | const hashedPassword = await bcrypt.hash(password, 10) 164 | 165 | const newUser = await User.create({ 166 | firstName, 167 | lastName, 168 | email, 169 | password: hashedPassword, 170 | imageName: req.file ? req.file.filename : "default-user-image.jpg" 171 | }) 172 | 173 | res.render("register", { successMessage: "account successfully created!" }) 174 | } 175 | } catch (error) { 176 | next(error) 177 | } 178 | } 179 | 180 | exports.handleLogout = (req, res, next) => { 181 | req.logOut() 182 | res.redirect("/topics") 183 | } 184 | --------------------------------------------------------------------------------