├── .DS_Store ├── .babelrc ├── .codeclimate.yml ├── .coveralls.yml ├── .dockerignore ├── .env.sample ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .hound.yml ├── .sequelizerc ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── docker-compose.yml ├── docs ├── assets │ └── css │ │ ├── index.css │ │ └── privacy-policy.css ├── docs.json ├── index.html └── privacy-policy.html ├── package.json └── src ├── UserService ├── User.js └── jwt.js ├── app.js ├── config └── config.js ├── controllers ├── admin.js ├── commentController.js ├── country │ └── country.js ├── ethnicGroup.js ├── food │ └── food.js ├── historicalFacts │ └── historicalFacts.js ├── index.js ├── music.js ├── newsletter │ ├── newsletter.js │ └── subscriber.js ├── resetPasswordController.js ├── state.js ├── touristCenter │ └── touristCenter.js └── user │ └── user.js ├── database ├── config │ ├── config.js │ ├── google-passport-signup.js │ ├── googlePassport-signin.js │ └── passport.js ├── migrations │ ├── 20200903144113-create-countries.js │ ├── 20200903150725-create-ethnic-group.js │ ├── 20200903160620-create-user.js │ ├── 20200904124754-create-profile.js │ ├── 20200906143952-create-tourist-center.js │ ├── 20200906145335-create-state.js │ ├── 20200908133515-create-music.js │ ├── 20200908133557-create-food.js │ ├── 20200914124552-create-comment.js │ ├── 20200914130656-create-newsletter.js │ ├── 20200918114512-create-historicalfact.js │ ├── 20201015112042-create-subscriber.js │ └── 20201015160026-create-newsletter-subscriber.js └── seeders │ ├── 20200904133535-User.js │ ├── 20200904133732-Profile.js │ ├── 20200904135720-countries.js │ ├── 20200904140351-ethnic-group.js │ ├── 20200905140416-historicalfact.js │ ├── 20200906160720-state.js │ ├── 20200906162725-touristCenter.js │ ├── 20200907150538-Music.js │ ├── 20200907151410-Food.js │ ├── 20200914145022-comment.js │ ├── 20200914145035-newsletter.js │ ├── 20201015112608-subscriber.js │ └── 20201015163227-newsletter_subscriber.js ├── middlewares ├── authenticate.js └── index.js ├── models ├── User.js ├── comment.js ├── countries.js ├── ethnicgroup.js ├── food.js ├── historicalfact.js ├── index.js ├── music.js ├── newsletter.js ├── newsletter_subscriber.js ├── profile.js ├── state.js ├── subscriber.js └── touristCenter.js ├── routes ├── adminRoutes │ └── activateUserRoutes.js ├── commentRoutes.js ├── countryRoute │ └── countryRoutes.js ├── docsRoutes │ └── docsRoute.js ├── ethnicgroup.js ├── foodRoute │ └── foodRoutes.js ├── historicalFactsRoute │ └── historicalFactsRoute.js ├── index.js ├── musicRoutes.js ├── newsletterRoute │ └── newsletterRoutes.js ├── resetPasswordRoutes.js ├── stateRoutes.js ├── touristCenterRoute │ └── touristCenterRoutes.js └── userRoute │ └── userRoutes.js ├── services ├── AdminServices │ ├── activateUser.js │ ├── countryService.js │ ├── ethnicgroup.js │ ├── historicalFactsService.js │ ├── musicService.js │ ├── stateService.js │ └── touristCenterService.js ├── UserService │ └── User.js ├── commentServices │ └── index.js ├── foodServices │ └── food.js └── newsletterServices │ ├── newsletter.js │ └── subsciber.js ├── tests ├── __mock__ │ ├── commentMockData.js │ ├── countriesMockData.js │ └── countryMockData.js ├── controllers │ ├── admin │ │ ├── activateUser.test.js │ │ ├── addCountry.test.js │ │ ├── addState-data.js │ │ ├── addState.test.js │ │ ├── addcountry-data.js │ │ └── deActivateUser.test.js │ ├── ethnicGroup │ │ ├── ethnicGroup-data.js │ │ └── ethnicGroup.test.js │ ├── food │ │ ├── food.data.js │ │ └── food.test.js │ ├── historicalFacts │ │ ├── historicalFacts.data.js │ │ └── historicalFacts.test.js │ ├── music │ │ ├── music-data.js │ │ └── music.test.js │ ├── newsletter │ │ ├── newsletter-test-data.js │ │ └── newsletter.test.js │ ├── resetPasswordTest │ │ ├── reset-test-data.js │ │ └── resetPasswordWithSinonTest.js │ ├── states │ │ ├── state-data.js │ │ └── state.test.js │ ├── touristCenter │ │ ├── touristCenter-data.js │ │ └── touristCenter.test.js │ ├── userProfileTest │ │ ├── profile-data.js │ │ └── profile-test.js │ ├── userSignUpTest │ │ ├── user-test-data.js │ │ └── user-test.js │ └── users │ │ ├── user-sign-in-test-data.js │ │ ├── user-sign-in-test.js │ │ ├── user-test-data.js │ │ └── user-test.js ├── google-oauth │ ├── google-mock-data.js │ └── google-test.spec.js ├── index-test.js ├── index.js ├── intergration │ ├── CommentsRoutes.spec.js │ └── CountriesRoutes.spec.js ├── models │ ├── State.spec.js │ ├── TouristCenter.spec.js │ ├── comment.spec.js │ ├── country.js │ ├── country.spec.js │ ├── ethnicgroup.spec.js │ ├── food.spec.js │ ├── historicalfact.test.js │ ├── music.spec.js │ ├── newsletter.spec.js │ ├── profile.spec.js │ └── user.spec.js └── unit │ └── controllers │ └── CountriesController.spec.js ├── utilities ├── Jwt.js ├── hashPassword.js ├── index.js ├── sendgrid.js ├── signToken.js └── util.js └── validation ├── commentValidation.js ├── countryValidation.js ├── ethnicgroup.js ├── foodValidation.js ├── historicalFactsValidation.js ├── index.js ├── musicValidation.js ├── newsletterValidation.js ├── stateValidation.js ├── touristCenterValidation.js └── userValidation.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devcareer/code-jammers-backend/49192b232b9e9a116509c4af55a398c4f46bebcb/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [ 4 | ["@babel/transform-runtime"] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | plugins: 3 | eslint: 4 | enabled: true 5 | channel: "eslint-5" 6 | config: 7 | config: ".eslintrc.json" 8 | exclude_patterns: 9 | - "src/database/" 10 | - "coverage" 11 | - "src/models" 12 | - "src/tests" 13 | - "src/utilities" 14 | - "src/validation" 15 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: vMCuKr5VnwoZkEi0e499SlRqm86H5OgqK 2 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 3 | github-token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | build 4 | 5 | pgdata 6 | 7 | .env.example 8 | 9 | .gitignore 10 | 11 | .git -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | DATABASE_URL = postgres://postgres:yourDatabasePassword@localhost:5432/prod_db 2 | DEV_DATABASE_URL = postgres://postgres:yourDatabasePassword@localhost:5432/dev_db 3 | TEST_DATABASE_URL =postgres://postgres:yourDatabasePassword@localhost:5432/test_db 4 | JWT_KEY = // add your secret 5 | GOOGLE_CLIENT_ID = // add your google clientId 6 | GOOGLE_CLIENT_SECRET = // add your google clientSecret 7 | GOOGLE_CALLBACK_URL = // add your google callBack 8 | COOKIE_KEY = // add your COOKIE_KEY 9 | JWT_KEY = secret 10 | SENDGRID_API_KEY= "your API KEY goes here" 11 | SENDGRID_EMAIL = "your SENDGRID EMAIL goes here" 12 | NODE_ENV = development 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .vscode/** 4 | .nyc_output/ 5 | coverage 6 | src/database/ 7 | src/models -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "airbnb-base", 4 | "env": { 5 | "node": true, 6 | "es6": true, 7 | "mocha": true 8 | }, 9 | "parser": "babel-eslint", 10 | "parserOptions": { 11 | "ecmaVersion": 2015, 12 | "sourceType": "module", 13 | "allowImportExportEverywhere": true 14 | }, 15 | "rules": { 16 | "linebreak-style": 0, 17 | "quotes": ["error", "double"], 18 | "semi": ["error", "always"], 19 | "no-unused-vars":"off", 20 | "no-plusplus":"off", 21 | "no-undef":"off", 22 | "comma-dangle":"off", 23 | "consistent-return":"off", 24 | "no-tabs":"off", 25 | "prefer-const":"off", 26 | "max-len":"off", 27 | "global-require":"off", 28 | "import/no-unresolved":"off", 29 | "import/prefer-default-export":"off", 30 | "import/no-dynamic-require":"off", 31 | "no-console": "off", 32 | "camelcase":"off", 33 | "import/no-extraneous-dependencies":["error", {"devDependencies": true}], 34 | "arrow-parens": [2, "as-needed"], 35 | "no-useless-catch": "off", 36 | "no-use-before-define": ["error", { "functions": false, "classes": false }], 37 | "require-jsdoc": 0, 38 | "no-useless-concat": 0, 39 | "operator-linebreak": 0, 40 | "valid-jsdoc": "error" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | name: NodeJS CI 4 | on: ["push"] 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: [12.x, 14.x] 12 | container: 13 | image: node:10.18-jessie 14 | services: 15 | postgres: 16 | image: postgres 17 | env: 18 | POSTGRES_USER: postgres 19 | POSTGRES_PASSWORD: postgres 20 | POSTGRES_DB: postgres 21 | options: >- 22 | --health-cmd pg_isready 23 | --health-interval 10s 24 | --health-timeout 5s 25 | --health-retries 5 26 | ports: 27 | - 5432:5432 28 | 29 | steps: 30 | - uses: actions/checkout@v2 31 | - name: Use Node.js ${{ matrix.node-version }} 32 | uses: actions/setup-node@v1 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | - run: npm install 36 | - run: npm run build --if-present 37 | - name: Connect to PostgreSQL 38 | 39 | run: node src/models/index 40 | env: 41 | POSTGRES_HOST: postgres 42 | POSTGRES_PORT: 5432 43 | 44 | - run: npm run coverage 45 | env: 46 | NODE_ENV: test 47 | JWT_KEY: ${{ secrets.JWT_KEY }} 48 | GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} 49 | GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} 50 | GOOGLE_CALLBACK_URL: ${{ secrets.GOOGLE_CALLBACK_URL }} 51 | COOKIE_KEY: ${{ secrets.COOKIE_KEY }} 52 | - name: Coveralls 53 | uses: coverallsapp/github-action@master 54 | env: 55 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 56 | COVERALLS_GIT_BRANCH: ${{ github.ref }} 57 | with: 58 | github-token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # environment variables 5 | .env 6 | 7 | # coverage dependencies 8 | coverage/ 9 | .nyc_output/ 10 | # package-lock.json 11 | package-lock.json 12 | 13 | # history 14 | .history 15 | 16 | build/ 17 | 18 | .DS_store 19 | 20 | sendgrid.env 21 | 22 | #docker compose 23 | pgdata -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | eslint: 2 | enabled: true 3 | config_file: "./.eslintrc.json" 4 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | require("@babel/register"); 3 | 4 | module.exports = { 5 | config: path.resolve('src/database/config', 'config.js'), 6 | 'models-path': path.resolve('src', 'models'), 7 | 'seeders-path': path.resolve('src/database', 'seeders'), 8 | 'migrations-path': path.resolve('src/database', 'migrations') 9 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "eslint.validate": [ 6 | "javascript" 7 | ], 8 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 9 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | COPY .env . 12 | 13 | 14 | EXPOSE 3000 15 | 16 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Devcareer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/badge/Reviewed_by-Hound-blueviolet.svg)](https://houndci.com) [![Actions Status](https://github.com/devcareer/code-jammers-backend/workflows/Nodejs%20CI/badge.svg)](https://github.com/devcareer/code-jammers-backend/actions) [![Maintainability](https://api.codeclimate.com/v1/badges/4f89b447ad9e9ead60db/maintainability)](https://codeclimate.com/github/devcareer/code-jammers-backend/maintainability) [![Coverage Status](https://coveralls.io/repos/github/devcareer/code-jammers-backend/badge.svg?branch=develop)](https://coveralls.io/github/devcareer/code-jammers-backend?branch=develop) 2 | 3 | 4 | # Know Africa 5 | 6 | An information and fact-based web application about Africa. Africa isn't a country. Africa is a continent with 54 independent countries. 7 | 8 | - For the Features, go here: https://github.com/devcareer/code-jammers-backend/wiki/Backend-sample-test-APIs 9 | - For the Heroku hosted APIs, go here: https://know-africa.herokuapp.com 10 | - For the Pivotal tracker board(project management), go here: https://www.pivotaltracker.com/n/projects/2463429 11 | - For GitHub kanban board, go here: https://github.com/devcareer/code-jammers-backend/projects/1 12 | - For our static website, go here: https://devcareer.github.io/code-jammers-backend/docs/ 13 | - For our swagger docs, go here: https://know-africa.herokuapp.com/api/v1/docs 14 | 15 | ### The technologies used in creating this project are: 16 | 17 | JavaScript, Node.js, ExpressJs, Sequelize ORM, PostgreSQL, Git, Github Actions, Code Climate, Coveralls, Pivotal Tracker, Docker, Swagger 18 | 19 | ### :rocket: How to get started 20 | 21 | - Make sure to have Git and Node.js installed on your computer 22 | - You can use this link to clone the project: `https://github.com/devcareer/code-jammers-backend.git` 23 | - cd into the project and run `npm install` 24 | - create a `.env` file and the contents in the sample file to it. 25 | - Run `npm run start-dev` to start the server and `npm test` to run the test suites 26 | 27 | # 28 | 29 | Note: To test the APIs using Heroku, use the above Heroku Link. 30 | 31 | # 32 | 33 | ### Then to test it through a container(docker), follow the following steps: 34 | 35 | - Make sure to have Git and Node.js installed on your computer 36 | - You can use this link to clone the project: `https://github.com/devcareer/code-jammers-backend.git` 37 | - cd into the project and run `npm install` 38 | - create a `.env` file and the contents in the sample file to it. 39 | - Run `docker-compose up` 40 | - Run `docker-compose run web npm run migration` 41 | - Run `docker-compose run web npm run seeder` 42 | - To run tests `docker-compose run web npm run test` 43 | - To shut down the container `docker-compose down` 44 | 45 | # 46 | 47 | ### Sample .env file format 48 | 49 | ``` 50 | DATABASE_URL = postgres://:@localhost:5432/ 51 | DEV_DATABASE_URL = postgres://:@localhost:5432/ 52 | TEST_DATABASE_URL = postgres://:@localhost:5432/ 53 | JWT_KEY = secret 54 | SENDGRID_API_KEY= 55 | SENDGRID_EMAIL = 56 | NODE_ENV = development 57 | GOOGLE_CLIENT_ID = 732603686075-gjth9b4tsh2qba48eh7mn7plcsjbfn4v.apps.googleusercontent.com 58 | GOOGLE_CLIENT_SECRET = ledPWdG3zGMCMzQ7wQvSvXyg 59 | GOOGLE_CALLBACK_URL_SIGNIN = http://localhost:3000/auth/google/signin 60 | GOOGLE_CALLBACK_URL_SIGNUP = http://localhost:3000/auth/google/signup 61 | COOKIE_KEY = cookieKey 62 | ``` 63 | 64 | ### Software Engineers / Mentees 65 | 66 | - Fiyin Akinsiku 67 | - Ufuoma Ogodo 68 | - Godspower Uche 69 | - Bernard Namangala 70 | - Donald Agbakoba 71 | - Bislon Zulu 72 | - Augusta Ehihebolo 73 | - Francis Xavier Abonyi 74 | - Oyindamola Abiola 75 | 76 | ### Author & Mentor 77 | 78 | - Funmilayo Olaiya. 79 | 80 | ### Acknowledgements 81 | 82 | - DevCareer 83 | 84 | ### To Contact Us: 85 | codejammers1@gmail.com 86 | 87 | ### License: 88 | 89 | This project is available under the MIT license. 90 | 91 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildpacks": [ 3 | 4 | { 5 | "url": "heroku/nodejs" 6 | } 7 | ], 8 | "env": { 9 | "NPM_CONFIG_PRODUCTION": "false" 10 | }, 11 | "repository": "https://github.com/devcareer/code-jammers-backend" 12 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | db: 4 | image: postgres 5 | restart: always 6 | environment: 7 | POSTGRES_USER: postgres 8 | POSTGRES_PASSWORD: Devcareers 9 | POSTGRES_DB: know_africa_docker_db 10 | volumes: 11 | - ./pgdata:/var/lib/postgresql/data 12 | ports: 13 | - "5432:5432" 14 | web: 15 | image: know-africa 16 | build: . 17 | depends_on: 18 | - db 19 | environment: 20 | DATABASE_URL: postgres://postgres:Devcareers@db:5432/know_africa_docker_db 21 | NODE_ENV: production 22 | PORT: 3000 23 | ports: 24 | - "80:3000" -------------------------------------------------------------------------------- /docs/assets/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 2%; 3 | font-family: 'Arial'; 4 | } 5 | 6 | .container { 7 | width: 80%; 8 | } -------------------------------------------------------------------------------- /docs/assets/css/privacy-policy.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 2%; 3 | font-family: 'Arial'; 4 | } 5 | 6 | .container { 7 | width: 80%; 8 | } -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

This page will contain core and relevant links pertaining to the Know Africa project.

10 | 11 |

The short version

12 | 13 |

14 | We are a team of learning software engineers at DevCareer, and we built an application called Know Africa. 15 | It is not a real project, such that whatever data is collected is possibly ours used in testing some OAuth applications. 16 |

21 |

22 |
23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-jammers-backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/app.js", 6 | "dependencies": { 7 | "@sendgrid/mail": "^7.2.6", 8 | "async": "^3.2.0", 9 | "bcrypt": "^5.0.0", 10 | "cookie-session": "^1.4.0", 11 | "cors": "^2.8.5", 12 | "cross-env": "^7.0.2", 13 | "dotenv": "^8.2.0", 14 | "express": "^4.17.1", 15 | "joi": "^17.2.1", 16 | "jsonwebtoken": "^8.5.1", 17 | "nodemailer": "^6.4.14", 18 | "nodemon": "^2.0.4", 19 | "passport": "^0.4.1", 20 | "passport-google-oauth20": "^2.0.0", 21 | "path": "^0.12.7", 22 | "pg": "^8.3.3", 23 | "pg-hstore": "^2.3.3", 24 | "proxyquire": "^2.1.3", 25 | "sequelize": "^5.21.13", 26 | "sequelize-cli": "^6.2.0", 27 | "swagger-ui-express": "^4.1.4" 28 | }, 29 | "devDependencies": { 30 | "@babel/cli": "^7.11.5", 31 | "@babel/core": "^7.11.5", 32 | "@babel/node": "^7.10.5", 33 | "@babel/plugin-transform-runtime": "^7.11.5", 34 | "@babel/preset-env": "^7.11.5", 35 | "@babel/register": "^7.11.5", 36 | "@babel/runtime": "^7.11.2", 37 | "babel-eslint": "^10.1.0", 38 | "chai": "^4.2.0", 39 | "chai-http": "^4.3.0", 40 | "coveralls": "^3.1.0", 41 | "cross-env": "^7.0.2", 42 | "eslint": "^7.12.0", 43 | "eslint-config-airbnb-base": "^14.2.0", 44 | "eslint-plugin-import": "^2.22.0", 45 | "eslint-plugin-jsdoc": "^30.7.5", 46 | "mocha": "^8.1.3", 47 | "mocha-lcov-reporter": "^1.3.0", 48 | "nodemon": "^2.0.4", 49 | "nyc": "^15.1.0", 50 | "sequelize-test-helpers": "^1.2.3", 51 | "sinon": "^9.0.3", 52 | "sinon-chai": "^3.5.0" 53 | }, 54 | "scripts": { 55 | "clean": "npm rm -rf build && npm run build-babel", 56 | "build-babel": "babel -d ./build ./src -s", 57 | "build": "npm run clean && npm run build-babel", 58 | "start": "npm run build && node ./build/app.js", 59 | "start-dev": "cross-env NODE_ENV=development nodemon --exec babel-node ./src/app.js", 60 | "test": "cross-env NODE_ENV=test && npm run migration && npm run seeder && nyc --require --reporter=html --reporter=text mocha -r @babel/register ./src/tests/index.js --timeout 50000 --recursive --exit || true ", 61 | "coverage": "nyc npm run test && nyc report --reporter=text-lcov --reporter=lcov | node ./node_modules/coveralls/bin/coveralls.js --verbose", 62 | "lint": "eslint src/ --fix", 63 | "migration": "npm run migrate:undo && npm run migrate", 64 | "migrate": "node_modules/.bin/sequelize db:migrate", 65 | "migrate:undo": "node_modules/.bin/sequelize db:migrate:undo:all", 66 | "seeder": "npm run seed:undo && npm run seed", 67 | "seed": "node_modules/.bin/sequelize db:seed:all", 68 | "seed:undo": "node_modules/.bin/sequelize db:seed:undo:all" 69 | }, 70 | "engines": { 71 | "node": "12.x" 72 | }, 73 | "repository": { 74 | "type": "git", 75 | "url": "git+https://github.com/devcareer/code-jammers-backend.git" 76 | }, 77 | "author": "", 78 | "license": "ISC", 79 | "bugs": { 80 | "url": "https://github.com/devcareer/code-jammers-backend/issues" 81 | }, 82 | "homepage": "https://github.com/devcareer/code-jammers-backend#readme" 83 | } 84 | -------------------------------------------------------------------------------- /src/UserService/User.js: -------------------------------------------------------------------------------- 1 | import database from "../models"; 2 | 3 | export default class User { 4 | static async createUser(newUser) { 5 | try { 6 | return await database.Users.create(newUser); 7 | } catch (error) { 8 | throw error; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/UserService/jwt.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import dotenv from "dotenv"; 3 | 4 | dotenv.config(); 5 | const secretKey = process.env.JWT_KEY; 6 | export default class jwtHelper { 7 | static async generateToken(payload, secret = secretKey) { 8 | const token = await jwt.sign(payload, secret, { expiresIn: "1d" }); 9 | return token; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-concat */ 2 | import express from "express"; 3 | import passport from "passport"; 4 | import cookieSession from "cookie-session"; 5 | import cors from "cors"; 6 | import dotenv from "dotenv"; 7 | import { googleStrategySignUp } from "./database/config/google-passport-signup"; 8 | import { googleStrategySignIn } from "./database/config/googlePassport-signin"; 9 | 10 | import router from "./routes/index"; 11 | 12 | dotenv.config(); 13 | 14 | const swaggerUi = require("swagger-ui-express"); 15 | 16 | const app = express(); 17 | const port = process.env.PORT || 3000; 18 | 19 | app.use(express.json()); 20 | app.use(cors()); 21 | app.use("/api/v1/", router); 22 | 23 | app.use(cookieSession({ 24 | maxAge: 24 * 60 * 60 * 1000, 25 | keys: process.env.COOKIE_KEY, 26 | })); 27 | app.use(passport.initialize()); 28 | app.use(passport.session()); 29 | 30 | passport.use("googleSignIn", googleStrategySignIn); 31 | passport.use("googleSignUp", googleStrategySignUp); 32 | 33 | passport.serializeUser((user, done) => { 34 | done(null, user); 35 | }); 36 | passport.deserializeUser((user, done) => { 37 | done(null, user); 38 | }); 39 | 40 | app.get( 41 | "/auth/google/signin", 42 | passport.authenticate("googleSignIn", { 43 | scope: ["profile", "email"], 44 | }), 45 | (req, res) => { 46 | if (!req.user.error) { 47 | res.status(200).json(req.user); 48 | } else { 49 | res.status(404).json(req.user); 50 | } 51 | } 52 | ); 53 | 54 | app.get( 55 | "/auth/google/signup", 56 | passport.authenticate("googleSignUp", { 57 | scope: ["profile", "email"], 58 | }), 59 | (req, res) => { 60 | if (!req.user.error) { 61 | res.redirect("/"); 62 | } else { 63 | res.status(409).json(req.user); 64 | } 65 | } 66 | ); 67 | 68 | app.get("/", (req, res) => { 69 | res.send("Welcome to Know Africa. Our privacy policy can be found here: https://devcareer.github.io/code-jammers-backend/docs/"); 70 | }); 71 | 72 | app.listen(port, () => { 73 | console.log(`Server Running on: ${port}`); 74 | }); 75 | 76 | export default app; 77 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | module.exports = { 4 | development: { 5 | url: process.env.DEV_DATABASE_URL, 6 | dialect: "postgres", 7 | }, 8 | test: { 9 | url: process.env.TEST_DATABASE_URL, 10 | dialect: "postgres", 11 | }, 12 | production: { 13 | url: process.env.DATABASE_URL, 14 | dialect: "postgres", 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/controllers/admin.js: -------------------------------------------------------------------------------- 1 | import User from "../services/AdminServices/activateUser"; 2 | 3 | /** 4 | * @class UserController 5 | * @description create, verify and log in user 6 | * @exports UserController 7 | */ 8 | export default class AdminController { 9 | /** 10 | * @param {object} req - The user request object 11 | * @param {object} res - The user response object 12 | * @returns {object} Success message 13 | */ 14 | static async ActivateUser(req, res) { 15 | try { 16 | const { id } = req.params; 17 | const updatedUser = await User.activateUser(id); 18 | res.status(200).json({ 19 | status: 200, 20 | message: "User activated successfully!", 21 | data: { 22 | email: updatedUser[1].email, username: updatedUser[1].username, verified: updatedUser[1].verified, active: updatedUser[1].active 23 | } 24 | }); 25 | } catch (error) { 26 | return res.status(500).json({ status: 500, error: "Server Error" }); 27 | } 28 | } 29 | 30 | /** 31 | * @param {object} req - The user request object 32 | * @param {object} res - The user response object 33 | * @returns {object} Success message 34 | */ 35 | static async DeActivateUser(req, res) { 36 | try { 37 | const { id } = req.params; 38 | const updatedUser = await User.deActivateUser(id); 39 | res.status(200).json({ 40 | status: 200, 41 | message: "User De-activated successfully!", 42 | data: { 43 | email: updatedUser[1].email, username: updatedUser[1].username, verified: updatedUser[1].verified, active: updatedUser[1].active 44 | } 45 | }); 46 | } catch (error) { 47 | return res.status(500).json({ status: 500, error: "Server Error" }); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/controllers/commentController.js: -------------------------------------------------------------------------------- 1 | import commentServices from "../services/commentServices"; 2 | import commentValidator from "../validation/commentValidation"; 3 | import db from "../models"; 4 | 5 | export default class commentController { 6 | /** 7 | * allows a user to make a comment 8 | * @param {object} req - request object 9 | * @param {object} res - response object 10 | * @returns {object} success message | error 11 | */ 12 | static async comment(req, res) { 13 | try { 14 | const { id } = req.decoded.user; 15 | const { relatedId } = req.params; 16 | const { comment } = req.body; 17 | const newComment = { comment, userId: id, relatedId }; 18 | const { error } = commentValidator(newComment); 19 | if (error) { 20 | return res.status(400).json({ status: 400, error: error.message }); 21 | } 22 | const createdComment = await commentServices.addComment(newComment); 23 | return res.status(201).json({ status: 201, message: "Comment has been added", data: createdComment }); 24 | } catch (error) { 25 | return res.status(500).json({ status: 500, error: "Server error." }); 26 | } 27 | } 28 | 29 | /** 30 | * @param {object} req - The user request object 31 | * @param {object} res - The user response object 32 | * @returns {object} Success message 33 | */ 34 | static async updateComment(req, res) { 35 | try { 36 | const { id } = req.params; 37 | const comment = await commentServices.getComment(id); 38 | if (!comment) return res.status(404).json({ status: 404, error: "Resource not found." }); 39 | if (comment.userId !== req.decoded.user.id) { 40 | return res.status(401).json({ status: 401, error: "You are not authorized to perform this action", }); 41 | } 42 | const result = await db.Comments.update(req.body, { 43 | where: { id }, 44 | returning: true 45 | }); 46 | return res.status(200).json({ status: 200, message: "Successfully updated comment", data: result[1][0].get() }); 47 | } catch (error) { 48 | return res.status(500).json({ status: 500, error: "Server error." }); 49 | } 50 | } 51 | 52 | /** 53 | * @param {object} req - The user request object 54 | * @param {object} res - The user response object 55 | * @returns {object} Success message 56 | */ 57 | static async getComment(req, res) { 58 | try { 59 | const { id } = req.params; 60 | const comment = await commentServices.getComment(id); 61 | if (!comment) return res.status(404).json({ status: 404, error: "Resource not found.", }); 62 | return res.status(200).json({ status: 200, message: "Successfully retrived comment", data: comment }); 63 | } catch (error) { 64 | return res.status(500).json({ status: 500, error: "Server error." }); 65 | } 66 | } 67 | 68 | /** 69 | * @param {object} req - The user request object 70 | * @param {object} res - The user response object 71 | * @returns {object} Success message 72 | */ 73 | static async deleteComment(req, res) { 74 | try { 75 | const { id } = req.params; 76 | const comment = await commentServices.getComment(id); 77 | if (!comment) return res.status(404).json({ status: 404, error: "Resource not found." }); 78 | if (comment.userId !== req.decoded.user.id) { 79 | return res.status(401).json({ status: 401, error: "You are not authorized to perform this action.", }); 80 | } 81 | await comment.destroy({ cascade: true }); 82 | return res.status(200).json({ status: 200, message: "Successfully deleted comment", }); 83 | } catch (error) { 84 | return res.status(500).json({ status: 500, error: "Server error." }); 85 | } 86 | } 87 | 88 | /** 89 | * @param {object} req - The user request object 90 | * @param {object} res - The user response object 91 | * @returns {object} Success message 92 | */ 93 | static async getUsersComments(req, res) { 94 | try { 95 | const { id } = req.decoded.user; 96 | const comments = await commentServices.getAllComments(id); 97 | return res.status(200).json({ status: 200, message: "Successfully retrived comments", data: comments }); 98 | } catch (error) { 99 | return res.status(500).json({ status: 500, error: "Server Error" }); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/controllers/index.js: -------------------------------------------------------------------------------- 1 | import "./user/user"; 2 | import resetPasswordController from "./resetPasswordController"; 3 | import countriesController from "./country/country"; 4 | import "./touristCenter/touristCenter"; 5 | import commentController from "./commentController"; 6 | 7 | export default { 8 | resetPasswordController, 9 | countriesController, 10 | commentController 11 | }; 12 | -------------------------------------------------------------------------------- /src/controllers/newsletter/newsletter.js: -------------------------------------------------------------------------------- 1 | import Util from "../../utilities/util"; 2 | import newsletterValidation from "../../validation/newsletterValidation"; 3 | import Newsletter from "../../services/newsletterServices/newsletter"; 4 | import Subscriber from "../../services/newsletterServices/subsciber"; 5 | import sendGrid from "../../utilities/sendgrid"; 6 | 7 | const util = new Util(); 8 | 9 | /** 10 | * @class Newsletters 11 | * @description create newsletter 12 | * @exports Newsletters 13 | */ 14 | export default class Newsletters { 15 | /** 16 | * @param {object} req - The newsletter request object 17 | * @param {object} res - The newsletter response object 18 | * @returns {object} Success message 19 | */ 20 | static async createNewsletter(req, res) { 21 | try { 22 | const { error } = newsletterValidation(req.body); 23 | if (error) { 24 | util.setError(400, "Validation Error", error.message); 25 | return util.send(res); 26 | } 27 | const { title, message } = req.body; 28 | const newsletterDetails = { title, message }; 29 | const newsletters = await Newsletter.createNewsletter(newsletterDetails); 30 | const getSubscribers = await Subscriber.subscribers(); 31 | if (getSubscribers) { 32 | getSubscribers.forEach(async element => { 33 | await sendGrid.sendNewsletter(element.email, element.firstName, title, message); 34 | await Subscriber.receivedMail(element.email, newsletterDetails.title); 35 | await Newsletter.updateNewsletterId(element.id, newsletters.dataValues.id); 36 | }); 37 | } 38 | if (newsletters) { 39 | return res.status(201).json({ status: 201, message: "Newsletter created!", data: newsletters }); 40 | } 41 | } catch (error) { 42 | return res.status(500).json({ status: 500, error: "Server Error" }); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/controllers/newsletter/subscriber.js: -------------------------------------------------------------------------------- 1 | import { subscriberValidation } from "../../validation/userValidation"; 2 | import Subscriber from "../../services/newsletterServices/subsciber"; 3 | import Util from "../../utilities/util"; 4 | import sendGrid from "../../utilities/sendgrid"; 5 | 6 | const util = new Util(); 7 | 8 | /** 9 | * @class subscriber 10 | * @description create subscriber, verify subscriber, get all subscribers, unsubscribe a subscriber 11 | * @exports subscriber 12 | */ 13 | export default class subscriber { 14 | /** 15 | * @param {object} req - The subscriber request object 16 | * @param {object} res - The subscriber response object 17 | * @returns {object} Success message 18 | */ 19 | static async createSubscriber(req, res) { 20 | try { 21 | const { error } = subscriberValidation(req.body); 22 | if (error) { 23 | util.setError(400, "Validation Error", error.message); 24 | return util.send(res); 25 | } 26 | const { email, firstName } = req.body; 27 | const Email = email.toLowerCase(); 28 | const emailExist = await Subscriber.emailExist(Email); 29 | if (emailExist) return res.status(409).json({ status: 409, error: "Sorry, You're already subscribed to this newsletter." }); 30 | const subscriberDetails = { email: Email, firstName }; 31 | await sendGrid.sendVerificationEmail(Email, firstName, "subscriber"); 32 | const subscribedUser = await Subscriber.subscribe(subscriberDetails); 33 | return res.status(201).json({ status: 201, message: "Please verify that you own this email", data: subscribedUser }); 34 | } catch (error) { 35 | return res.status(500).json({ status: 500, error: "Server Error" }); 36 | } 37 | } 38 | 39 | /** 40 | * @param {object} req - The subscriber request object 41 | * @param {object} res - The subscriber response object 42 | * @returns {object} - Success message 43 | */ 44 | static async verifySubscriber(req, res) { 45 | try { 46 | const { email } = req.params; 47 | const updatedSubscriber = await Subscriber.updateSubscriberVerification(email); 48 | res.status(200).json({ status: 200, message: "Yay!!! You just subscribed successfully", data: { email: updatedSubscriber[1].email, verified: updatedSubscriber[1].verified } }); 49 | } catch (error) { 50 | return res.status(500).json({ status: 500, error: "Server Error" }); 51 | } 52 | } 53 | 54 | /** 55 | * @param {object} req - The subscriber request object 56 | * @param {object} res - The subscriber response object 57 | * @returns {object} Success message 58 | */ 59 | static async allSubscribers(req, res) { 60 | try { 61 | const getSubscribers = await Subscriber.subscribers(); 62 | if (getSubscribers.length > 0) { 63 | return res.status(200).json({ status: 200, message: "Subscribers Retrieved", data: getSubscribers }); 64 | } 65 | return res.status(404).json({ status: 404, message: "No Subscriber found" }); 66 | } catch (error) { 67 | return res.status(500).json({ status: 500, error: "Server Error" }); 68 | } 69 | } 70 | 71 | /** 72 | * @param {object} req - The subscriber request object 73 | * @param {object} res - The subscriber response object 74 | * @returns {object} Success message 75 | */ 76 | static async unsubscribe(req, res) { 77 | try { 78 | const { email } = req.params; 79 | if (!email) return res.status(400).json({ status: 400, error: "Please enter your email address." }); 80 | const Email = email.toLowerCase(); 81 | const emailExist = await Subscriber.emailExist(Email); 82 | if (!emailExist) return res.status(404).json({ status: 404, error: "Subscriber not found" }); 83 | const unsubsrcibedUser = await Subscriber.unsubscribe(emailExist); 84 | if (unsubsrcibedUser) return res.status(200).json({ status: 200, message: "You've unsubscribed from this newsletter", data: email }); 85 | } catch (error) { 86 | return res.status(500).json({ status: 500, error: "Server Error" }); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/controllers/resetPasswordController.js: -------------------------------------------------------------------------------- 1 | import db from "../models/index"; 2 | import signToken from "../utilities/signToken"; 3 | import hashPassword from "../utilities/hashPassword"; 4 | import sendGrid from "../utilities/sendgrid"; 5 | 6 | require("dotenv").config(); 7 | const jwt = require("jsonwebtoken"); 8 | 9 | let hostURL; 10 | 11 | /** 12 | * @class 13 | * @description recover and reset 14 | * @exports 15 | */ 16 | export default class { 17 | /** 18 | * @param {object} req - The user request object 19 | * @param {object} res - The user response object 20 | * @returns {object} Success message 21 | */ 22 | static async recover(req, res) { 23 | try { 24 | const user = await db.Users.findOne({ where: { email: req.body.email }, }); 25 | if (!user) { 26 | return res.status(404).json({ status: 404, error: `The email address ${req.body.email} is not associated with any account.`, }); 27 | } 28 | if (!user.verified) { 29 | return res.status(403).json({ status: 403, error: "The account is not verified. Please check your email inbox for verification email.", }); 30 | } 31 | const signed = signToken(user.toJSON(), user.password); 32 | await sendGrid.sendResetPasswordEmail(user.email, user.id, signed, res); 33 | return res.status(200).json({ status: 200, message: "A reset email has been sent" }); 34 | } catch (error) { 35 | return res.status(500).json({ status: 500, error: "Server error", }); 36 | } 37 | } 38 | 39 | /** 40 | * @param {object} req - The reset request object 41 | * @param {object} res - The reset response object 42 | * @returns {object} Success message 43 | */ 44 | static async reset(req, res) { 45 | const { id, token } = req.params; 46 | const { newPassword } = req.body; 47 | db.Users.findOne({ where: { id }, }) 48 | .then(user => { 49 | if (!user) { return res.send({ status: 404, error: "user does not exist" }); } try { 50 | jwt.verify(token, user.password); 51 | } catch (error) { 52 | return res.send({ status: 410, error: "link has expired or has been used. please request for a new link." }); 53 | } 54 | const hashedPass = hashPassword(newPassword); 55 | try { 56 | db.Users.update({ password: hashedPass, }, { 57 | where: { id: user.id }, returning: true, plain: true 58 | }); 59 | return res.status(200).json({ status: 200, success: "password has been reset" }); 60 | } catch (error) { 61 | console.log(error); 62 | return res.status(500).json({ status: 500, error: "Server error" }); 63 | } 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/database/config/config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | module.exports = { 4 | development: { 5 | url: process.env.DEV_DATABASE_URL, 6 | dialect: "postgres", 7 | }, 8 | test: { 9 | username: "postgres", 10 | password: "postgres", 11 | database: "postgres", 12 | host: "postgres", 13 | dialect: "postgres" 14 | }, 15 | production: { 16 | url: process.env.DATABASE_URL, 17 | dialect: "postgres", 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/database/config/google-passport-signup.js: -------------------------------------------------------------------------------- 1 | import { Strategy } from "passport-google-oauth20"; 2 | import User from "../../services/UserService/User"; 3 | import model from "../../models"; 4 | import dotenv from "dotenv"; 5 | 6 | const { Users } = model; 7 | 8 | dotenv.config(); 9 | 10 | const googleStrategySignUp = new Strategy( 11 | { 12 | clientID: process.env.GOOGLE_CLIENT_ID, 13 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 14 | callbackURL: process.env.GOOGLE_CALLBACK_HEROKU_URL_SIGNUP, 15 | callbackURL: process.env.GOOGLE_CALLBACK_URL_SIGNUP, 16 | }, 17 | 18 | async (accessToken, refreshToken, profile, done) => { 19 | try { 20 | const userExist = await Users.findOne({ 21 | where: { 22 | googleId: profile.id, 23 | }, 24 | }); 25 | 26 | if (userExist) { 27 | const msgObj = { status: 409, error: "User already exist" }; 28 | return done(null, msgObj); 29 | } 30 | 31 | const newUserDetail = await User.createUser({ 32 | username: profile.name.givenName, 33 | lastName: profile.name.familyName, 34 | profilePicture: profile.photos[0].value, 35 | password: "", 36 | email: profile.emails[0].value, 37 | googleId: profile.id, 38 | provider: "google", 39 | role: "User", 40 | verified: true, 41 | }); 42 | return done(null, newUserDetail); 43 | } catch (err) { 44 | return done(err, false); 45 | } 46 | } 47 | ); 48 | 49 | export { googleStrategySignUp }; 50 | -------------------------------------------------------------------------------- /src/database/config/googlePassport-signin.js: -------------------------------------------------------------------------------- 1 | import { Strategy } from "passport-google-oauth20"; 2 | import model from "../../models"; 3 | import dotenv from "dotenv"; 4 | import jwtHelper from "../../utilities/Jwt"; 5 | 6 | const { generateToken } = jwtHelper; 7 | const { Users } = model; 8 | 9 | dotenv.config(); 10 | 11 | const googleStrategySignIn = new Strategy( 12 | { 13 | clientID: process.env.GOOGLE_CLIENT_ID, 14 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 15 | callbackURL: process.env.GOOGLE_CALLBACK_HEROKU_URL_SIGNIN, 16 | callbackURL: process.env.GOOGLE_CALLBACK_URL_SIGNIN, 17 | }, 18 | 19 | async (accessToken, refreshToken, profile, done) => { 20 | try { 21 | const userExist = await Users.findOne({ 22 | where: { 23 | googleId: profile.id, 24 | }, 25 | }); 26 | 27 | if (userExist) { 28 | const token = await generateToken({ userExist }); 29 | const msgObj = { status: 200, message: "User Logged in!", token }; 30 | return done(null, msgObj); 31 | } 32 | const error = { status: 404, error: "User with this account does not exist." } 33 | return done(null, error); 34 | } catch (err) { 35 | return done(err, false); 36 | } 37 | } 38 | ); 39 | 40 | export { googleStrategySignIn }; 41 | -------------------------------------------------------------------------------- /src/database/config/passport.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devcareer/code-jammers-backend/49192b232b9e9a116509c4af55a398c4f46bebcb/src/database/config/passport.js -------------------------------------------------------------------------------- /src/database/migrations/20200903144113-create-countries.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable require-jsdoc */ 2 | export default { 3 | up(queryInterface, Sequelize) { 4 | return queryInterface.sequelize 5 | .query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") 6 | .then(() => queryInterface.createTable("Countries", { 7 | id: { 8 | allowNull: false, 9 | primaryKey: true, 10 | type: Sequelize.UUID, 11 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 12 | }, 13 | nameOfCountry: { 14 | allowNull: false, 15 | type: Sequelize.STRING, 16 | }, 17 | gallery: { 18 | allowNull: false, 19 | type: Sequelize.STRING, 20 | }, 21 | capital: { 22 | allowNull: false, 23 | type: Sequelize.STRING, 24 | }, 25 | population: { 26 | allowNull: false, 27 | type: Sequelize.INTEGER, 28 | }, 29 | officialLanguage: { 30 | allowNull: false, 31 | type: Sequelize.STRING, 32 | }, 33 | region: { 34 | allowNull: false, 35 | type: Sequelize.STRING, 36 | }, 37 | currency: { 38 | allowNull: false, 39 | type: Sequelize.STRING, 40 | }, 41 | createdAt: { 42 | allowNull: false, 43 | type: Sequelize.DATE, 44 | }, 45 | updatedAt: { 46 | allowNull: false, 47 | type: Sequelize.DATE, 48 | }, 49 | })); 50 | }, 51 | down: queryInterface => queryInterface.dropTable("Countries"), 52 | }; 53 | -------------------------------------------------------------------------------- /src/database/migrations/20200903150725-create-ethnic-group.js: -------------------------------------------------------------------------------- 1 | export default { 2 | up(queryInterface, Sequelize) { 3 | return queryInterface.sequelize 4 | .query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") 5 | .then(() => queryInterface.createTable("EthnicGroups", { 6 | id: { 7 | allowNull: false, 8 | primaryKey: true, 9 | type: Sequelize.UUID, 10 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 11 | }, 12 | countryId: { 13 | type: Sequelize.UUID, 14 | references: { 15 | model: "Countries", 16 | key: "id", 17 | }, 18 | }, 19 | name: { 20 | allowNull: false, 21 | type: Sequelize.STRING, 22 | }, 23 | festivals: { 24 | allowNull: false, 25 | type: Sequelize.STRING, 26 | }, 27 | dressing: { 28 | allowNull: false, 29 | type: Sequelize.TEXT, 30 | }, 31 | language: { 32 | allowNull: false, 33 | type: Sequelize.STRING, 34 | }, 35 | gallery: { 36 | allowNull: false, 37 | type: Sequelize.STRING, 38 | }, 39 | culturalPractices: { 40 | allowNull: false, 41 | type: Sequelize.TEXT, 42 | }, 43 | createdAt: { 44 | allowNull: false, 45 | type: Sequelize.DATE, 46 | }, 47 | updatedAt: { 48 | allowNull: false, 49 | type: Sequelize.DATE, 50 | }, 51 | })); 52 | }, 53 | down: queryInterface => queryInterface.dropTable("EthnicGroups"), 54 | }; 55 | -------------------------------------------------------------------------------- /src/database/migrations/20200903160620-create-user.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable require-jsdoc */ 2 | module.exports = { 3 | up(queryInterface, Sequelize) { 4 | return queryInterface.sequelize.query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";").then(() => queryInterface.createTable("Users", { 5 | id: { 6 | allowNull: false, 7 | primaryKey: true, 8 | type: Sequelize.UUID, 9 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 10 | }, 11 | email: { 12 | type: Sequelize.STRING, 13 | }, 14 | username: { 15 | type: Sequelize.STRING, 16 | }, 17 | password: { 18 | type: Sequelize.STRING, 19 | }, 20 | verified: { 21 | type: Sequelize.BOOLEAN, 22 | defaultValue: false, 23 | }, 24 | googleId: { 25 | type: Sequelize.STRING, 26 | }, 27 | role: { 28 | type: Sequelize.ENUM("Super Admin", "Admin", "User"), 29 | defaultValue: "User", 30 | }, 31 | active: { 32 | type: Sequelize.BOOLEAN, 33 | defaultValue: true, 34 | }, 35 | createdAt: { 36 | allowNull: false, 37 | type: Sequelize.DATE, 38 | }, 39 | updatedAt: { 40 | allowNull: false, 41 | type: Sequelize.DATE, 42 | }, 43 | })); 44 | }, 45 | down: queryInterface => queryInterface.dropTable("Users"), 46 | }; 47 | -------------------------------------------------------------------------------- /src/database/migrations/20200904124754-create-profile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable require-jsdoc */ 2 | module.exports = { 3 | up(queryInterface, Sequelize) { 4 | return queryInterface.sequelize.query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";").then(() => queryInterface.createTable("Profiles", { 5 | id: { 6 | allowNull: false, 7 | primaryKey: true, 8 | type: Sequelize.UUID, 9 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 10 | }, 11 | userId: { 12 | type: Sequelize.STRING, 13 | allowNull: false, 14 | }, 15 | firstName: { 16 | type: Sequelize.STRING, 17 | }, 18 | lastName: { 19 | type: Sequelize.STRING, 20 | }, 21 | profilePicture: { 22 | type: Sequelize.STRING, 23 | }, 24 | createdAt: { 25 | allowNull: false, 26 | type: Sequelize.DATE, 27 | }, 28 | updatedAt: { 29 | allowNull: false, 30 | type: Sequelize.DATE, 31 | }, 32 | })); 33 | }, 34 | down: queryInterface => queryInterface.dropTable("Profiles"), 35 | }; 36 | -------------------------------------------------------------------------------- /src/database/migrations/20200906143952-create-tourist-center.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable require-jsdoc */ 2 | export default { 3 | up(queryInterface, Sequelize) { 4 | return queryInterface.sequelize 5 | .query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") 6 | .then(() => queryInterface.createTable("TouristCenters", { 7 | countryId: { 8 | type: Sequelize.UUID, 9 | allowNull: false, 10 | }, 11 | id: { 12 | allowNull: false, 13 | primaryKey: true, 14 | type: Sequelize.UUID, 15 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 16 | }, 17 | name: { 18 | type: Sequelize.STRING, 19 | }, 20 | location: { 21 | type: Sequelize.STRING, 22 | }, 23 | gallery: { 24 | type: Sequelize.STRING, 25 | }, 26 | about: { 27 | type: Sequelize.TEXT 28 | }, 29 | createdAt: { 30 | allowNull: false, 31 | type: Sequelize.DATE, 32 | }, 33 | updatedAt: { 34 | allowNull: false, 35 | type: Sequelize.DATE, 36 | }, 37 | })); 38 | }, 39 | down: async (queryInterface, Sequelize) => { 40 | await queryInterface.dropTable("TouristCenters"); 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/database/migrations/20200906145335-create-state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | up(queryInterface, Sequelize) { 3 | return queryInterface.sequelize 4 | .query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";') 5 | .then(() => 6 | queryInterface.createTable("States", { 7 | countryId: { 8 | type: Sequelize.UUID, 9 | allowNull: false, 10 | }, 11 | id: { 12 | allowNull: false, 13 | primaryKey: true, 14 | type: Sequelize.UUID, 15 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 16 | }, 17 | name: { 18 | type: Sequelize.STRING, 19 | }, 20 | capital: { 21 | type: Sequelize.STRING, 22 | }, 23 | gallery: { 24 | type: Sequelize.STRING, 25 | }, 26 | createdAt: { 27 | allowNull: false, 28 | type: Sequelize.DATE, 29 | }, 30 | updatedAt: { 31 | allowNull: false, 32 | type: Sequelize.DATE, 33 | }, 34 | }) 35 | ); 36 | }, 37 | down: async (queryInterface, Sequelize) => { 38 | await queryInterface.dropTable("States"); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /src/database/migrations/20200908133515-create-music.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => queryInterface.sequelize 3 | .query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") 4 | .then(() => queryInterface.createTable("Music", { 5 | id: { 6 | allowNull: false, 7 | primaryKey: true, 8 | type: Sequelize.UUID, 9 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 10 | }, 11 | countryId: { 12 | allowNull: false, 13 | type: Sequelize.UUID, 14 | }, 15 | category: { 16 | allowNull: false, 17 | type: Sequelize.STRING, 18 | }, 19 | information: { 20 | allowNull: false, 21 | type: Sequelize.STRING(5000), 22 | }, 23 | gallery: { 24 | allowNull: false, 25 | type: Sequelize.ARRAY(Sequelize.STRING), 26 | defaultValue: [] 27 | }, 28 | createdAt: { 29 | allowNull: false, 30 | type: Sequelize.DATE, 31 | }, 32 | updatedAt: { 33 | allowNull: false, 34 | type: Sequelize.DATE, 35 | }, 36 | })), 37 | down: async queryInterface => queryInterface.dropTable("Music"), 38 | }; 39 | -------------------------------------------------------------------------------- /src/database/migrations/20200908133557-create-food.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => queryInterface.sequelize 3 | .query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") 4 | .then(() => queryInterface.createTable("Foods", { 5 | id: { 6 | allowNull: false, 7 | primaryKey: true, 8 | type: Sequelize.UUID, 9 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 10 | }, 11 | countryId: { 12 | type: Sequelize.UUID, 13 | allowNull: false, 14 | }, 15 | type: { 16 | allowNull: false, 17 | type: Sequelize.STRING, 18 | }, 19 | methodOfPreparation: { 20 | allowNull: false, 21 | type: Sequelize.TEXT, 22 | }, 23 | gallery: { 24 | allowNull: false, 25 | type: Sequelize.STRING, 26 | }, 27 | createdAt: { 28 | allowNull: false, 29 | type: Sequelize.DATE, 30 | }, 31 | updatedAt: { 32 | allowNull: false, 33 | type: Sequelize.DATE, 34 | }, 35 | })), 36 | down: async queryInterface => queryInterface.dropTable("Foods"), 37 | }; 38 | -------------------------------------------------------------------------------- /src/database/migrations/20200914124552-create-comment.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => queryInterface.sequelize 3 | .query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") 4 | .then(() => queryInterface.createTable("Comments", { 5 | id: { 6 | allowNull: true, 7 | primaryKey: true, 8 | type: Sequelize.UUID, 9 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 10 | }, 11 | userId: { 12 | allowNull: false, 13 | type: Sequelize.STRING, 14 | }, 15 | relatedId:{ 16 | allowNull: false, 17 | type: Sequelize.UUID, 18 | }, 19 | comment: { 20 | allowNull: false, 21 | type: Sequelize.STRING, 22 | }, 23 | createdAt: { 24 | allowNull: false, 25 | type: Sequelize.DATE, 26 | }, 27 | updatedAt: { 28 | allowNull: false, 29 | type: Sequelize.DATE, 30 | }, 31 | })), 32 | down: async (queryInterface, Sequelize) => { 33 | await queryInterface.dropTable("Comments"); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /src/database/migrations/20200914130656-create-newsletter.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => queryInterface.sequelize 3 | .query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") 4 | .then(() => queryInterface.createTable("Newsletters", { 5 | id: { 6 | allowNull: false, 7 | primaryKey: true, 8 | type: Sequelize.UUID, 9 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 10 | }, 11 | title: { 12 | type: Sequelize.STRING, 13 | }, 14 | message: { 15 | type: Sequelize.TEXT, 16 | }, 17 | createdAt: { 18 | allowNull: false, 19 | type: Sequelize.DATE, 20 | }, 21 | updatedAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE, 24 | }, 25 | })), 26 | down: async (queryInterface, Sequelize) => { 27 | await queryInterface.dropTable("Newsletters"); 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/database/migrations/20200918114512-create-historicalfact.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => queryInterface.sequelize 3 | .query("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") 4 | .then(() => queryInterface.createTable("Historicalfacts", { 5 | id: { 6 | allowNull: false, 7 | primaryKey: true, 8 | type: Sequelize.UUID, 9 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 10 | }, 11 | countryId: { 12 | allowNull: false, 13 | type: Sequelize.UUID, 14 | }, 15 | about: { 16 | allowNull: false, 17 | type: Sequelize.TEXT, 18 | }, 19 | location: { 20 | allowNull: false, 21 | type: Sequelize.STRING, 22 | }, 23 | gallery: { 24 | allowNull: false, 25 | type: Sequelize.STRING, 26 | }, 27 | createdAt: { 28 | allowNull: false, 29 | type: Sequelize.DATE, 30 | }, 31 | updatedAt: { 32 | allowNull: false, 33 | type: Sequelize.DATE, 34 | }, 35 | })), 36 | down: async queryInterface => queryInterface.dropTable("Historicalfact"), 37 | }; 38 | -------------------------------------------------------------------------------- /src/database/migrations/20201015112042-create-subscriber.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable("Subscribers", { 4 | id: { 5 | allowNull: false, 6 | primaryKey: true, 7 | type: Sequelize.UUID, 8 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 9 | }, 10 | email: { 11 | type: Sequelize.STRING 12 | }, 13 | firstName: { 14 | type: Sequelize.STRING 15 | }, 16 | verified: { 17 | type: Sequelize.BOOLEAN, 18 | defaultValue: false, 19 | }, 20 | newsletter: { 21 | type: Sequelize.ARRAY(Sequelize.STRING), 22 | defaultValue: ["Welcome to Know Africa"] 23 | }, 24 | createdAt: { 25 | allowNull: false, 26 | type: Sequelize.DATE 27 | }, 28 | updatedAt: { 29 | allowNull: false, 30 | type: Sequelize.DATE 31 | } 32 | }); 33 | }, 34 | down: async queryInterface => queryInterface.dropTable("Subscribers") 35 | }; 36 | -------------------------------------------------------------------------------- /src/database/migrations/20201015160026-create-newsletter-subscriber.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.createTable("Newsletter_Subscribers", { 4 | id: { 5 | allowNull: false, 6 | primaryKey: true, 7 | type: Sequelize.UUID, 8 | defaultValue: Sequelize.literal("uuid_generate_v4()"), 9 | }, 10 | newsletterId: { 11 | type: Sequelize.ARRAY(Sequelize.STRING), 12 | defaultValue: ["7cc6de97-2ed6-4422-9ce2-9ff22cc5e97f"] 13 | }, 14 | subscriberId: { 15 | type: Sequelize.STRING, 16 | allowNull: false, 17 | }, 18 | createdAt: { 19 | allowNull: false, 20 | type: Sequelize.DATE 21 | }, 22 | updatedAt: { 23 | allowNull: false, 24 | type: Sequelize.DATE 25 | } 26 | }); 27 | }, 28 | down: async (queryInterface, Sequelize) => { 29 | await queryInterface.dropTable("Newsletter_Subscribers"); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/database/seeders/20200904133535-User.js: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcrypt"; 2 | 3 | const password = "12345"; 4 | const hash = bcrypt.hashSync(password, 10); 5 | 6 | module.exports = { 7 | up: async (queryInterface, Sequelize) => { 8 | await queryInterface.bulkInsert("Users", [{ 9 | id: "98e0350f-ed09-46b0-83d7-8a135afeaf84", 10 | email: "francis@gmail.com", 11 | username: "iamfrancis", 12 | password: hash, 13 | role: "Super Admin", 14 | verified: true, 15 | active: true, 16 | createdAt: new Date(), 17 | updatedAt: new Date(), 18 | }, 19 | { 20 | id: "fc1f4e85-8e83-4a38-ab1e-8e4da2c6ddbb", 21 | email: "ufuoma@gmail.com", 22 | username: "bellogo", 23 | password: hash, 24 | role: "Admin", 25 | verified: true, 26 | active: true, 27 | createdAt: new Date(), 28 | updatedAt: new Date(), 29 | }, 30 | { 31 | id: "fc1f4e85-8e83-4a38-ab1e-8e4da2c6dd25", 32 | email: "fiyin@gmail.com", 33 | username: "fiyinsendev", 34 | password: hash, 35 | role: "User", 36 | verified: true, 37 | active: false, 38 | createdAt: new Date(), 39 | updatedAt: new Date(), 40 | }, 41 | { 42 | id: "57af7c29-efb2-434e-9fce-b87c77447aaa", 43 | email: "godspower@gmail.com", 44 | username: "therealgodspower", 45 | password: hash, 46 | role: "User", 47 | verified: true, 48 | active: true, 49 | createdAt: new Date(), 50 | updatedAt: new Date(), 51 | }], {}); 52 | }, 53 | 54 | down: async (queryInterface, Sequelize) => { 55 | 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /src/database/seeders/20200904133732-Profile.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert("Profiles", [{ 4 | firstName: "Olusola", 5 | lastName: "Fullstack", 6 | profilePicture: "http:/facebook.com", 7 | userId: "98e0350f-ed09-46b0-83d7-8a135afeaf84", 8 | createdAt: new Date(), 9 | updatedAt: new Date(), 10 | }, 11 | { 12 | firstName: "Olusola", 13 | lastName: "Fullstack", 14 | profilePicture: "http:/facebook.com", 15 | userId: "fc1f4e85-8e83-4a38-ab1e-8e4da2c6ddbb", 16 | createdAt: new Date(), 17 | updatedAt: new Date(), 18 | }, 19 | { 20 | firstName: "Olusola", 21 | lastName: "Fullstack", 22 | profilePicture: "http:/facebook.com", 23 | userId: "57af7c29-efb2-434e-9fce-b87c77447aaa", 24 | createdAt: new Date(), 25 | updatedAt: new Date(), 26 | }], {}); 27 | }, 28 | 29 | down: async (queryInterface, Sequelize) => { 30 | 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/database/seeders/20200904135720-countries.js: -------------------------------------------------------------------------------- 1 | export default { 2 | up: queryInterface => queryInterface.bulkInsert( 3 | "Countries", 4 | [ 5 | { 6 | id: "6003fb36-5112-463e-a1f9-c8944e72412f", 7 | nameOfCountry: "Nigeria", 8 | gallery: 9 | "https://img.freepik.com/free-vector/nigeria-map-flag-national-emblem_2239-230.jpg?size=338&ext=jpg", 10 | capital: "FCT Abuja", 11 | population: 205, 12 | officialLanguage: "English", 13 | region: "West Africa", 14 | currency: "Naira", 15 | createdAt: new Date(), 16 | updatedAt: new Date(), 17 | }, 18 | { 19 | id: "2e11e4a9-441b-4426-9521-39adc64ccfad", 20 | nameOfCountry: "Zambia", 21 | gallery: 22 | "https://cdn.pixabay.com/photo/2013/07/13/14/18/zambia-162464_960_720.png", 23 | capital: "Lusaka", 24 | population: 17351708, 25 | officialLanguage: "English", 26 | region: "Southern Africa", 27 | currency: "Kwacha", 28 | createdAt: new Date(), 29 | updatedAt: new Date(), 30 | }, 31 | ], 32 | {}, 33 | ), 34 | down: queryInterface => queryInterface.bulkDelete("Countries", null, {}), 35 | }; 36 | -------------------------------------------------------------------------------- /src/database/seeders/20200904140351-ethnic-group.js: -------------------------------------------------------------------------------- 1 | export default { 2 | up: queryInterface => queryInterface.bulkInsert( 3 | "EthnicGroups", 4 | [ 5 | { 6 | id: "63995ef8-351f-4035-a268-c6cd7697f0ef", 7 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 8 | name: "Yoruba", 9 | festivals: "Eyo festival", 10 | dressing: "The most commonly worn by women are Ìró (wrapper) and Bùbá (blouse–like loose top). They also have matching Gèlè (head gear) that must be put on whenever the Ìró and Bùbá is on. Just as the cap (Fìlà) is important to men, women’s dressing is considered incomplete without Gèlè", 11 | language: "Yoruba", 12 | gallery: 13 | "https://unsplash.com/photos/eS_aZA5S42Y", 14 | culturalPractices: "Some Yoruba believe that a baby may come with pre-destined names. For instance, twins (ibeji) are believed to have natural-birth names. Thus the first to be born of the two is called Taiwo or 'Taiye', shortened forms of Taiyewo, meaning the taster of the world. This is to identify the first twin as the one sent by the other one to first go and taste the world. If he/she stays there, it follows that it is not bad, and that would send a signal to the other one to start coming. Hence the second to arrive is named Kehinde (late arrival; it is now common for many Kehindes to be called by the familiar diminutive 'Kenny'.Irrespective of the sex the child born to the same woman after the twins is called Idowu, and the one after this is called Alaba, then the next child is called Idogbe. The Yoruba believe that some children are born to die. This derives from the phenomenon of the tragic incidents of high rate of infant mortality sometimes afflicting the same family for a long time. When this occurs, the family devises all kinds of method to forestall a recurrence, including giving special names at a new birth.", 15 | createdAt: new Date(), 16 | updatedAt: new Date(), 17 | }, 18 | { 19 | id: "09015644-4195-417f-8934-7cdc6e8519e2", 20 | countryId: "2e11e4a9-441b-4426-9521-39adc64ccfad", 21 | name: "Bemba", 22 | festivals: "Ukusefya Pa Ngwena", 23 | dressing: "Before the arrival of Europeans, the most common type of cloth was made from bark. Women wore it around the waist as a loincloth. Today most Zambians, including the Bemba, wear modern clothes. Men wear Western clothing (shorts, pants, and shirts). However, the designs and fashions in women's dresses are usually of Zambian or African origin.", 24 | language: "Bemba", 25 | gallery: 26 | "https://unsplash.com/photos/eS_aZA5S42Y", 27 | culturalPractices: "When a man and women are married the man goes to live with the wife's family and so generations are traced in a matrilineal fashion, as opposed to the more common patrilineal lineages.", 28 | createdAt: new Date(), 29 | updatedAt: new Date(), 30 | }, 31 | ], 32 | {}, 33 | ), 34 | down: queryInterface => queryInterface.bulkDelete("EthnicGroups", null, {}), 35 | }; 36 | -------------------------------------------------------------------------------- /src/database/seeders/20200905140416-historicalfact.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert("Historicalfacts", [{ 4 | countryId: "2a7fe4a4-f6d3-4e99-a7ef-8098786073c2", 5 | about: "This is the history.", 6 | location: "Nigeria", 7 | gallery: "https://netstorage-legit", 8 | createdAt: new Date(), 9 | updatedAt: new Date(), 10 | }], 11 | {}); 12 | }, 13 | 14 | down: async (queryInterface, Sequelize) => { 15 | await queryInterface.bulkDelete("Historicalfacts", null, {}); 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/database/seeders/20200906160720-state.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert( 4 | "States", 5 | 6 | [ 7 | 8 | { 9 | countryId: "2e11e4a9-441b-4426-9521-39adc64ccfad", 10 | 11 | name: "Gwarimpa", 12 | 13 | capital: "Gwarimpa", 14 | 15 | gallery: 16 | "https://en.wikipedia.org/wiki/Lusaka#/media/File:Downtown_Lusaka.JPG", 17 | 18 | createdAt: new Date(), 19 | updatedAt: new Date(), 20 | }, 21 | ], 22 | 23 | {}, 24 | ); 25 | }, 26 | 27 | down: async (queryInterface, Sequelize) => { 28 | await queryInterface.bulkDelete("States", null, {}); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/database/seeders/20200906162725-touristCenter.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert("TouristCenters", [{ 4 | id: "12adedc3-d529-4f67-9ee6-5b763d5010f4", 5 | countryId: "2e11e4a9-441b-4426-9521-39adc64ccfad", 6 | name: "Ufuoma Memorial", 7 | location: "Livingstone", 8 | gallery: 9 | "https://cdn.pixabay.com/photo/2017/04/13/09/11/waterfall-2227010_960_720.jpg", 10 | about: "Victoria Falls is a waterfall on the Zambezi River in southern Africa, which provides habitat for several unique species of plants and animals. It is located on the border between Zambia and Zimbabwe[1] and is considered to be one of the world's largest waterfalls due to its width of 1,708 metres", 11 | createdAt: new Date(), 12 | updatedAt: new Date(), 13 | }, 14 | ], {}); 15 | }, 16 | 17 | down: async (queryInterface, Sequelize) => { 18 | await queryInterface.bulkDelete("TouristCenters", null, {}); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/database/seeders/20200907150538-Music.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert( 4 | "Music", [{ 5 | id: "2a7fe4a4-f6d3-4e99-a7ef-8098786073c2", 6 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 7 | category: "Apala", 8 | information: "Apala is a music genre originally developed by the Yoruba people of Nigeria, during the country's history as a colony of the British Empire. It is a percussion-based style that originated in the late 1930s.", 9 | gallery: ["https://res.cloudinary.com/augustar/image/upload/v1599565560/music_cl7glf.jpg"], 10 | createdAt: new Date(), 11 | updatedAt: new Date(), 12 | }], 13 | {}, 14 | ); 15 | }, 16 | down: async (queryInterface, Sequelize) => { 17 | await queryInterface.bulkDelete("Music", null, {}); 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/database/seeders/20200907151410-Food.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert( 4 | "Foods", [{ 5 | id: "9e92fadd-8897-4d3d-b9de-cf82e9752a1f", 6 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 7 | type: "Egusi soup", 8 | methodOfPreparation: `Add melon, onion, water and fresh pepper in a blender. Blend the ingredients together until 9 | it forms a paste. 10 | Add water, chicken, turkey, ponmo, yellow pepper, stock, smoked panla fish, crayfish, chopped pepper and palm oil 11 | to a pot. Allow all the ingredients to boil for 15 minutes. 12 | Now scoop little bits of the egusi paste into the pot. Do not stir in the mixture, just cover the pot and allow it 13 | to cook for 10 minutes. 14 | Next add the Ugu leaves and Uziza, mix together and allow to cook for 5 minutes. You will notice that the oil will 15 | start to rise to the top, your Egusi Soup is now ready. 16 | Serve and enjoy with Eba, Semo, Pounded Yam or even Rice.`, 17 | gallery: "https://res.cloudinary.com/augustar/image/upload/v1599563547/pounded-yam_qfzcy7.jpg", 18 | createdAt: new Date(), 19 | updatedAt: new Date(), 20 | }], 21 | {}, 22 | ); 23 | }, 24 | down: async (queryInterface, Sequelize) => { 25 | await queryInterface.bulkDelete("Foods", null, {}); 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/database/seeders/20200914145022-comment.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert("Comments", [{ 4 | id: "9ccff1f3-135f-41d9-adf2-b92c8ade4d02", 5 | userId: "98e0350f-ed09-46b0-83d7-8a135afeaf84", 6 | relatedId: "6003fb36-5112-463e-a1f9-c8944e72412f", 7 | comment: "Never knew Africa was not a country. Wow, thanks.", 8 | createdAt: new Date(), 9 | updatedAt: new Date(), 10 | }, 11 | { 12 | id: "c375c640-81ff-405a-89a8-460ea2f71756", 13 | userId: "98e0350f-ed09-46b0-83d7-8a135afeaf84", 14 | comment: "Nigeria is a beautiful Country.", 15 | relatedId: "6003fb36-5112-463e-a1f9-c8944e72412f", 16 | createdAt: new Date(), 17 | updatedAt: new Date(), 18 | }]); 19 | }, 20 | 21 | down: async (queryInterface, Sequelize) => { 22 | await queryInterface.bulkDelete("Comments", null, {}); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/database/seeders/20200914145035-newsletter.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert("Newsletters", [{ 4 | id: "7cc6de97-2ed6-4422-9ce2-9ff22cc5e97f", 5 | title: "Welcome message", 6 | message: "Thank you for subscribing to Know Africa. We look forward to sharing the many beauties of Africa with you!", 7 | createdAt: new Date(), 8 | updatedAt: new Date(), 9 | }, 10 | { 11 | id: "a430e505-937b-4908-9422-7aa57044e85c", 12 | title: "Africa Not a country", 13 | message: "Africa is home to over 1.5 billion humans with about 54 independent countries, forests, natural resources, game, music, rich culture, and women! Visiting any country in Africa would give you a deep awakening on how divers, unique and beautiful Africa is.", 14 | createdAt: new Date(), 15 | updatedAt: new Date(), 16 | }, 17 | { 18 | id: "866e1211-5b39-4cd4-9edf-865dd90bd360", 19 | title: "Africa is beautiful", 20 | message: "Visiting any of the 54 countries in Africa will definitely change your crude perspective that Africa is yet to catch up with civilization!", 21 | createdAt: new Date(), 22 | updatedAt: new Date(), 23 | } 24 | ]); 25 | }, 26 | 27 | down: async (queryInterface, Sequelize) => { 28 | await queryInterface.bulkDelete("Newsletters", null, {}); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/database/seeders/20201015112608-subscriber.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | up: async (queryInterface, Sequelize) => { 3 | await queryInterface.bulkInsert("Subscribers", [{ 4 | id: "6cbaa746-6e42-453e-91f4-c0de15fb4b9a", 5 | firstName: "Donald", 6 | email: "donald@gmail.com", 7 | verified: true, 8 | newsletter: [ 9 | "Welcome to Know Africa", 10 | " Africa Not a country", 11 | " Africa is beautiful", 12 | ], 13 | createdAt: new Date(), 14 | updatedAt: new Date(), 15 | }, 16 | { 17 | id: "d3efec7e-480f-4cb3-b6d2-bed800b8989b", 18 | firstName: "Garry", 19 | email: "garry@gmail.com", 20 | verified: true, 21 | newsletter: [ 22 | "Welcome message", 23 | " Africa is beautiful", 24 | ], 25 | createdAt: new Date(), 26 | updatedAt: new Date(), 27 | }]); 28 | }, 29 | 30 | down: async (queryInterface, Sequelize) => { 31 | await queryInterface.bulkDelete("Subscribers", null, {}); 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /src/database/seeders/20201015163227-newsletter_subscriber.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-dupe-keys */ 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.bulkInsert("Newsletter_Subscribers", [ 5 | { 6 | subscriberId: "6cbaa746-6e42-453e-91f4-c0de15fb4b9a", 7 | newsletterId: [ 8 | "7cc6de97-2ed6-4422-9ce2-9ff22cc5e97f", 9 | " a430e505-937b-4908-9422-7aa57044e85c", 10 | " 866e1211-5b39-4cd4-9edf-865dd90bd360" 11 | ], 12 | createdAt: new Date(), 13 | updatedAt: new Date(), 14 | }, 15 | { 16 | subscriberId: "d3efec7e-480f-4cb3-b6d2-bed800b8989b", 17 | newsletterId: [ 18 | "7cc6de97-2ed6-4422-9ce2-9ff22cc5e97f", 19 | " 866e1211-5b39-4cd4-9edf-865dd90bd360" 20 | ], 21 | createdAt: new Date(), 22 | updatedAt: new Date(), 23 | } 24 | ]); 25 | }, 26 | 27 | down: async (queryInterface, Sequelize) => { 28 | await queryInterface.bulkDelete("Newsletter_Subscribers", null, {}); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | import "./authenticate"; 2 | -------------------------------------------------------------------------------- /src/models/User.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const User = sequelize.define("Users", { 3 | email: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | unique: true, 7 | }, 8 | username: { 9 | type: DataTypes.STRING, 10 | allowNull: false, 11 | unique: true, 12 | }, 13 | password: { 14 | type: DataTypes.STRING, 15 | allowNull: false, 16 | }, 17 | verified: { 18 | type: DataTypes.BOOLEAN, 19 | defaultValue: false, 20 | }, 21 | googleId: { 22 | type: DataTypes.STRING, 23 | allowNull: true, 24 | }, 25 | role: { 26 | type: DataTypes.ENUM("Super Admin", "Admin", "User"), 27 | defaultValue: "User", 28 | }, 29 | active: { 30 | type: DataTypes.BOOLEAN, 31 | defaultValue: true, 32 | }, 33 | }); 34 | return User; 35 | }; 36 | -------------------------------------------------------------------------------- /src/models/comment.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Comment = sequelize.define("Comments", { 3 | userId: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | }, 7 | comment: { 8 | type: DataTypes.TEXT, 9 | allowNull: false, 10 | }, 11 | relatedId:{ 12 | type: DataTypes.STRING, 13 | allowNull: false, 14 | } 15 | }); 16 | 17 | Comment.associate = models => { 18 | Comment.belongsTo(models.Users, { 19 | as: "user", 20 | foreignKey: "userId", 21 | onDelete: "cascade", 22 | }); 23 | Comment.belongsTo(models.Countries, { 24 | as: "countryComment", 25 | foreignKey: "relatedId", 26 | onDelete: "cascade", 27 | }); 28 | 29 | Comment.belongsTo(models.TouristCenters, { 30 | as: "touristCenterComment", 31 | foreignKey: "relatedId", 32 | onDelete: "cascade", 33 | }); 34 | Comment.belongsTo(models.EthnicGroups, { 35 | as: "ethnicGroupComment", 36 | foreignKey: "relatedId", 37 | onDelete: "cascade", 38 | }); 39 | Comment.belongsTo(models.Foods, { 40 | as: "foodComment", 41 | foreignKey: "relatedId", 42 | onDelete: "cascade", 43 | }); 44 | Comment.belongsTo(models.Historicalfacts, { 45 | as: "historicalFactsComment", 46 | foreignKey: "relatedId", 47 | onDelete: "cascade", 48 | }); 49 | Comment.belongsTo(models.Music, { 50 | as: "musicComment", 51 | foreignKey: "relatedId", 52 | onDelete: "cascade", 53 | }); 54 | }; 55 | return Comment; 56 | }; 57 | -------------------------------------------------------------------------------- /src/models/countries.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Country = sequelize.define("Countries", { 3 | nameOfCountry: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | }, 7 | gallery: { 8 | type: DataTypes.STRING, 9 | allowNull: false, 10 | }, 11 | capital: { 12 | type: DataTypes.STRING, 13 | allowNull: false, 14 | }, 15 | population: { 16 | type: DataTypes.INTEGER, 17 | allowNull: false, 18 | }, 19 | officialLanguage: { 20 | type: DataTypes.STRING, 21 | allowNull: false, 22 | }, 23 | region: { 24 | type: DataTypes.STRING, 25 | allowNull: false, 26 | }, 27 | currency: { 28 | type: DataTypes.STRING, 29 | allowNull: false, 30 | }, 31 | }); 32 | 33 | Country.associate = models => { 34 | Country.hasMany(models.EthnicGroups, { 35 | as: "ethnicGroups", 36 | foreignKey: "countryId", 37 | onDelete: "cascade", 38 | hooks: true, 39 | }); 40 | 41 | Country.hasMany(models.Foods, { 42 | as: "Food", 43 | foreignKey: "countryId", 44 | onDelete: "cascade", 45 | hooks: true, 46 | }); 47 | 48 | Country.hasMany(models.States, { 49 | as: "states", 50 | foreignKey: "countryId", 51 | onDelete: "cascade", 52 | hooks: true, 53 | }); 54 | 55 | Country.hasMany(models.TouristCenters, { 56 | as: "touristCenters", 57 | foreignKey: "countryId", 58 | onDelete: "cascade", 59 | hooks: true, 60 | }); 61 | 62 | Country.hasMany(models.Historicalfacts, { 63 | as: "historicalFacts", 64 | foreignKey: "countryId", 65 | onDelete: "cascade", 66 | hooks: true, 67 | }); 68 | 69 | Country.hasMany(models.Music, { 70 | as: "music", 71 | foreignKey: "countryId", 72 | }); 73 | 74 | Country.hasMany(models.Comments, { 75 | as: "comments", 76 | foreignKey: "relatedId", 77 | onDelete: 'cascade', 78 | hooks: true, 79 | }); 80 | }; 81 | 82 | 83 | 84 | return Country; 85 | }; 86 | -------------------------------------------------------------------------------- /src/models/ethnicgroup.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const EthnicGroup = sequelize.define("EthnicGroups", { 3 | countryId: { 4 | type: DataTypes.UUID, 5 | allowNull: false, 6 | references: { 7 | model: "Countries", 8 | key: "id", 9 | }, 10 | }, 11 | name: { 12 | type: DataTypes.STRING, 13 | allowNull: false, 14 | }, 15 | festivals: { 16 | type: DataTypes.STRING, 17 | allowNull: false, 18 | }, 19 | dressing: { 20 | type: DataTypes.TEXT, 21 | allowNull: false, 22 | }, 23 | language: { 24 | type: DataTypes.STRING, 25 | allowNull: false, 26 | }, 27 | gallery: { 28 | type: DataTypes.STRING, 29 | allowNull: false, 30 | }, 31 | culturalPractices: { 32 | type: DataTypes.TEXT, 33 | allowNull: false, 34 | }, 35 | }); 36 | 37 | EthnicGroup.associate = models => { 38 | EthnicGroup.belongsTo(models.Countries, { 39 | as: "countryEthnicGroup", 40 | foreignKey: "countryId", 41 | onDelete: "cascade", 42 | }); 43 | EthnicGroup.hasMany(models.Comments, { 44 | as: "comments", 45 | foreignKey: "relatedId", 46 | onDelete: 'cascade', 47 | hooks: true, 48 | }); 49 | }; 50 | return EthnicGroup; 51 | }; 52 | -------------------------------------------------------------------------------- /src/models/food.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Food = sequelize.define("Foods", { 3 | countryId: { 4 | type: DataTypes.UUID, 5 | allowNull: false, 6 | references: { 7 | model: "Countries", 8 | key: "id", 9 | }, 10 | }, 11 | type: { 12 | type: DataTypes.STRING, 13 | allowNull: false, 14 | }, 15 | methodOfPreparation: { 16 | type: DataTypes.TEXT, 17 | allowNull: false, 18 | }, 19 | gallery: { 20 | type: DataTypes.STRING, 21 | allowNull: false, 22 | }, 23 | }); 24 | Food.associate = models => { 25 | Food.belongsTo(models.Countries, { 26 | as: "country", 27 | foreignKey: "countryId", 28 | }); 29 | Food.hasMany(models.Comments, { 30 | as: "comments", 31 | foreignKey: "relatedId", 32 | onDelete: 'cascade', 33 | hooks: true, 34 | }); 35 | }; 36 | return Food; 37 | }; 38 | -------------------------------------------------------------------------------- /src/models/historicalfact.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Historicalfact = sequelize.define("Historicalfacts", { 3 | countryId: { 4 | type: DataTypes.UUID, 5 | allowNull: false, 6 | references: { 7 | model: "Countries", 8 | key: "id", 9 | }, 10 | }, 11 | about: { 12 | allowNull: false, 13 | type: DataTypes.TEXT, 14 | }, 15 | location: { 16 | type: DataTypes.STRING, 17 | allowNull: false, 18 | }, 19 | gallery: { 20 | type: DataTypes.STRING, 21 | allowNull: false, 22 | }, 23 | }); 24 | 25 | Historicalfact.associate = models => { 26 | Historicalfact.belongsTo(models.Countries, { 27 | as: "historicalFacts", 28 | foreignKey: "countryId", 29 | }); 30 | Historicalfact.hasMany(models.Comments, { 31 | as: "comments", 32 | foreignKey: "relatedId", 33 | onDelete: 'cascade', 34 | hooks: true, 35 | }); 36 | }; 37 | 38 | return Historicalfact; 39 | }; 40 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const Sequelize = require("sequelize"); 4 | require("dotenv").config(); 5 | 6 | const basename = path.basename(__filename); 7 | 8 | const env = process.env.NODE_ENV || "development"; 9 | 10 | const config = require(`${__dirname}/../database/config/config.js`)[env]; 11 | const db = {}; 12 | let sequelize; 13 | if (config.url) { 14 | sequelize = new Sequelize(config.url, config); 15 | } else { 16 | sequelize = new Sequelize( 17 | config.database, 18 | config.username, 19 | config.password, 20 | { 21 | host: config.host, 22 | dialect: "postgres" 23 | }, 24 | config 25 | ); 26 | } 27 | fs.readdirSync(__dirname) 28 | .filter( 29 | file => file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js" 30 | ) 31 | .forEach(file => { 32 | const model = sequelize.import(path.join(__dirname, file)); 33 | db[model.name] = model; 34 | }); 35 | Object.keys(db).forEach(modelName => { 36 | if (db[modelName].associate) { 37 | db[modelName].associate(db); 38 | } 39 | }); 40 | db.sequelize = sequelize; 41 | db.Sequelize = Sequelize; 42 | module.exports = db; 43 | -------------------------------------------------------------------------------- /src/models/music.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Music = sequelize.define("Music", { 3 | countryId: { 4 | type: DataTypes.UUID, 5 | allowNull: false, 6 | references: { 7 | model: "Countries", 8 | key: "id", 9 | }, 10 | }, 11 | category: { 12 | type: DataTypes.STRING, 13 | allowNull: false, 14 | }, 15 | information: { 16 | type: DataTypes.STRING(5000), 17 | allowNull: false, 18 | }, 19 | gallery: { 20 | type: DataTypes.ARRAY(DataTypes.STRING), 21 | defaultValue: [], 22 | allowNull: false, 23 | }, 24 | }); 25 | Music.associate = models => { 26 | Music.belongsTo(models.Countries, { 27 | as: "country", 28 | foreignKey: "countryId", 29 | }); 30 | 31 | Music.hasMany(models.Comments, { 32 | as: "comments", 33 | foreignKey: "relatedId", 34 | onDelete: 'cascade', 35 | hooks: true, 36 | }); 37 | }; 38 | return Music; 39 | }; 40 | -------------------------------------------------------------------------------- /src/models/newsletter.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Newsletter = sequelize.define("Newsletters", { 3 | title: { 4 | type: DataTypes.STRING, 5 | allowNull: true, 6 | }, 7 | message: { 8 | type: DataTypes.TEXT, 9 | allowNull: true, 10 | }, 11 | }); 12 | Newsletter.associate = models => { 13 | Newsletter.belongsToMany(models.Subscribers, { 14 | through: "Newsletter_Subscriber", 15 | as: "subscriber", 16 | foreignKey: "newsletterId", 17 | onDelete: "CASCADE", 18 | hooks: true 19 | }); 20 | }; 21 | return Newsletter; 22 | }; 23 | -------------------------------------------------------------------------------- /src/models/newsletter_subscriber.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Newsletter_Subscribers = sequelize.define("Newsletter_Subscribers", { 3 | subscriberId: { 4 | type: DataTypes.STRING, 5 | allowNull: true, 6 | references: { 7 | model: "Subscribers", 8 | key: "id", 9 | }, 10 | }, 11 | newsletterId: { 12 | type: DataTypes.ARRAY(DataTypes.STRING), 13 | allowNull: true, 14 | references: { 15 | model: "Newsletters", 16 | key: "id", 17 | }, 18 | } 19 | }); 20 | Newsletter_Subscribers.associate = models => { 21 | Newsletter_Subscribers.belongsTo(models.Subscribers, { 22 | as: "newsletterSubscriber", 23 | foreignKey: "subscriberId", 24 | onDelete: "CASCADE", 25 | hooks: true 26 | }); 27 | Newsletter_Subscribers.belongsTo(models.Newsletters, { 28 | as: "subscribedNewsletter", 29 | foreignKey: "newsletterId", 30 | onDelete: "CASCADE", 31 | hooks: true 32 | }); 33 | }; 34 | return Newsletter_Subscribers; 35 | }; 36 | -------------------------------------------------------------------------------- /src/models/profile.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Profile = sequelize.define("Profiles", { 3 | userId: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | references: { 7 | model: "Users", 8 | key: "id", 9 | }, 10 | }, 11 | firstName: { 12 | type: DataTypes.STRING, 13 | allowNull: true, 14 | }, 15 | lastName: { 16 | type: DataTypes.STRING, 17 | allowNull: true, 18 | }, 19 | profilePicture: { 20 | type: DataTypes.STRING, 21 | allowNull: true, 22 | }, 23 | }); 24 | Profile.associate = (models) => { 25 | Profile.belongsTo(models.Users, { 26 | as: "profileDetails", 27 | foreignKey: "userId", 28 | }); 29 | }; 30 | return Profile; 31 | }; 32 | -------------------------------------------------------------------------------- /src/models/state.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const State = sequelize.define("States", { 3 | name: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | }, 7 | countryId: { 8 | type: DataTypes.STRING, 9 | allowNull: false, 10 | }, 11 | gallery: { 12 | type: DataTypes.STRING, 13 | allowNull: false, 14 | }, 15 | capital: { 16 | type: DataTypes.STRING, 17 | allowNull: false, 18 | }, 19 | }); 20 | 21 | State.associate = models => { 22 | State.belongsTo(models.Countries, { 23 | as: "countryState", 24 | foreignKey: "countryId", 25 | }); 26 | 27 | State.hasMany(models.Comments, { 28 | as: "comments", 29 | foreignKey: "relatedId", 30 | onDelete: 'cascade', 31 | hooks: true, 32 | }); 33 | }; 34 | 35 | return State; 36 | }; 37 | -------------------------------------------------------------------------------- /src/models/subscriber.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const Subscriber = sequelize.define("Subscribers", { 3 | email: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | unique: true, 7 | }, 8 | firstName: { 9 | type: DataTypes.STRING, 10 | allowNull: false, 11 | }, 12 | verified: { 13 | type: DataTypes.BOOLEAN, 14 | defaultValue: false, 15 | }, 16 | newsletter: { 17 | type: DataTypes.ARRAY(DataTypes.STRING), 18 | defaultValue: ["Welcome to Know Africa"] 19 | } 20 | }); 21 | Subscriber.associate = models => { 22 | Subscriber.belongsToMany(models.Newsletters, { 23 | through: "Newsletter_Subscriber", 24 | as: "newsletters", 25 | foreignKey: "subscriberId", 26 | onDelete: "CASCADE", 27 | hooks: true 28 | }); 29 | }; 30 | return Subscriber; 31 | }; 32 | -------------------------------------------------------------------------------- /src/models/touristCenter.js: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) => { 2 | const TouristCenter = sequelize.define("TouristCenters", { 3 | countryId: { 4 | type: DataTypes.STRING, 5 | allowNull: false, 6 | }, 7 | name: { 8 | type: DataTypes.STRING, 9 | allowNull: false, 10 | }, 11 | gallery: { 12 | type: DataTypes.STRING, 13 | allowNull: false, 14 | }, 15 | location: { 16 | type: DataTypes.STRING, 17 | allowNull: false, 18 | }, 19 | about: { 20 | type: DataTypes.TEXT, 21 | allowNull: false, 22 | }, 23 | }); 24 | 25 | TouristCenter.associate = models => { 26 | TouristCenter.belongsTo(models.Countries, { 27 | as: "countryTouristCenter", 28 | foreignKey: "countryId", 29 | }); 30 | 31 | TouristCenter.hasMany(models.Comments, { 32 | as: "comments", 33 | foreignKey: "relatedId", 34 | onDelete: 'cascade', 35 | hooks: true, 36 | }); 37 | }; 38 | 39 | return TouristCenter; 40 | }; 41 | -------------------------------------------------------------------------------- /src/routes/adminRoutes/activateUserRoutes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import AdminController from "../../controllers/admin"; 3 | import Authentication from "../../middlewares/authenticate"; 4 | 5 | const router = Router(); 6 | const { ActivateUser, DeActivateUser } = AdminController; 7 | const { verifyToken, verifyAdmin } = Authentication; 8 | 9 | router.patch("/admin/activate-user/:id", verifyToken, verifyAdmin, ActivateUser); 10 | router.patch("/admin/deactivate-user/:id", verifyToken, verifyAdmin, DeActivateUser); 11 | 12 | export default router; 13 | -------------------------------------------------------------------------------- /src/routes/commentRoutes.js: -------------------------------------------------------------------------------- 1 | import Router from "express"; 2 | import controllers from "../controllers"; 3 | import Authentication from "../middlewares/authenticate"; 4 | 5 | const { verifyToken, verifyUserById } = Authentication; 6 | 7 | const { commentController } = controllers; 8 | 9 | const { 10 | comment, getComment, updateComment, deleteComment, getUsersComments 11 | } = commentController; 12 | const router = Router(); 13 | 14 | router.post("/comment/:relatedId", verifyToken, comment); 15 | router.get("/comment/:id", verifyToken, getComment); 16 | router.get("/comments", verifyToken, getUsersComments); 17 | router.patch("/comment/:id", verifyToken, updateComment); 18 | router.delete("/comment/:id", verifyToken, deleteComment); 19 | 20 | export default router; 21 | -------------------------------------------------------------------------------- /src/routes/countryRoute/countryRoutes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import AdminController from "../../controllers/country/country"; 3 | import Authentication from "../../middlewares/authenticate"; 4 | import controllers from "../../controllers"; 5 | 6 | const { countriesController } = controllers; 7 | const { 8 | listCountries, getCountry, deleteCountry, updateCountry 9 | } = countriesController; 10 | 11 | const { addCountry } = AdminController; 12 | const { verifyAdmin, verifyToken } = Authentication; 13 | 14 | const router = Router(); 15 | router.post("/admin/country", verifyToken, verifyAdmin, addCountry); 16 | router.get("/country/:id", getCountry); 17 | router.get("/countries", listCountries); 18 | router.delete("/admin/country/:id", verifyToken, verifyAdmin, deleteCountry); 19 | router.patch("/admin/country/:id", verifyToken, verifyAdmin, updateCountry); 20 | 21 | export default router; 22 | -------------------------------------------------------------------------------- /src/routes/docsRoutes/docsRoute.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import docs from "../../../docs/docs.json"; 3 | 4 | const swaggerUi = require("swagger-ui-express"); 5 | 6 | const router = Router(); 7 | 8 | router.get("/docs", swaggerUi.setup(docs)); 9 | 10 | export default router; 11 | -------------------------------------------------------------------------------- /src/routes/ethnicgroup.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import Authentication from "../middlewares/authenticate"; 3 | import EthnicGroups from "../controllers/ethnicGroup"; 4 | 5 | const { 6 | getAllEthnicGroups, 7 | getEthnicGroupById, 8 | deleteEthnicGroupById, 9 | createEthnicGroup, 10 | updateEthnicGroupById 11 | } = EthnicGroups; 12 | 13 | const { verifyAdmin, verifyToken } = Authentication; 14 | 15 | const router = Router(); 16 | router.delete("/admin/ethnic-group/:id", verifyToken, verifyAdmin, deleteEthnicGroupById); 17 | router.post("/admin/ethnic-group/:countryId", verifyToken, verifyAdmin, createEthnicGroup); 18 | router.patch("/admin/ethnic-group/:id", verifyToken, verifyAdmin, updateEthnicGroupById); 19 | router.get("/ethnic-groups", getAllEthnicGroups); 20 | router.get("/ethnic-group/:id", getEthnicGroupById); 21 | export default router; 22 | -------------------------------------------------------------------------------- /src/routes/foodRoute/foodRoutes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import Food from "../../controllers/food/food"; 3 | import Authentication from "../../middlewares/authenticate"; 4 | 5 | const { 6 | createFood, 7 | getAllFoods, 8 | getOneFoodById, 9 | updateFood, 10 | deleteFood 11 | } = Food; 12 | const { verifyAdmin, verifyToken } = Authentication; 13 | const router = Router(); 14 | router.post("/admin/food/:countryId", verifyToken, verifyAdmin, createFood); 15 | router.patch("/admin/food/:id", verifyToken, verifyAdmin, updateFood); 16 | router.delete("/admin/food/:id", verifyToken, verifyAdmin, deleteFood); 17 | router.get("/food", getAllFoods); 18 | router.get("/food/:id", getOneFoodById); 19 | 20 | export default router; 21 | -------------------------------------------------------------------------------- /src/routes/historicalFactsRoute/historicalFactsRoute.js: -------------------------------------------------------------------------------- 1 | import Router from "express"; 2 | import historicalFact from "../../controllers/historicalFacts/historicalFacts"; 3 | import Authentication from "../../middlewares/authenticate"; 4 | 5 | // eslint-disable-next-line max-len 6 | const { 7 | getAllHistoricalFacts, 8 | getHistoricalFact, 9 | getHistoricalFactByLocation, 10 | addHistoricalFact, 11 | updateHistoricalFact, 12 | deleteHistoricalFact 13 | } = historicalFact; 14 | const { verifyAdmin, verifyToken } = Authentication; 15 | 16 | const router = Router(); 17 | 18 | router.get("/historical-fact", getAllHistoricalFacts); 19 | router.get("/historical-fact/:id", getHistoricalFact); 20 | router.get("/historical-fact/location/:location", getHistoricalFactByLocation); 21 | router.post("/admin/historical-fact/:countryId", verifyToken, verifyAdmin, addHistoricalFact); 22 | router.patch("/admin/historical-fact/:id", verifyToken, verifyAdmin, updateHistoricalFact); 23 | router.delete("/admin/historical-fact/:id", verifyToken, verifyAdmin, deleteHistoricalFact); 24 | 25 | export default router; 26 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import stateRoutes from "./stateRoutes"; 3 | import resetPasswordRoutes from "./resetPasswordRoutes"; 4 | import userRoutes from "./userRoute/userRoutes"; 5 | import touristCenterRoutes from "./touristCenterRoute/touristCenterRoutes"; 6 | import newsletterRoutes from "./newsletterRoute/newsletterRoutes"; 7 | import ethnicRoutes from "./ethnicgroup"; 8 | import musicRoutes from "./musicRoutes"; 9 | import countryRoutes from "./countryRoute/countryRoutes"; 10 | import foodRoutes from "./foodRoute/foodRoutes"; 11 | import historicalFactsRoutes from "./historicalFactsRoute/historicalFactsRoute"; 12 | import commentRoutes from "./commentRoutes"; 13 | import adminRoutes from "./adminRoutes/activateUserRoutes"; 14 | import docRoutes from "./docsRoutes/docsRoute"; 15 | 16 | const swaggerUi = require("swagger-ui-express"); 17 | 18 | const router = new Router(); 19 | 20 | router.use("/", adminRoutes); 21 | router.use("/", swaggerUi.serve, docRoutes); 22 | router.use("/", countryRoutes); 23 | router.use("/", ethnicRoutes); 24 | router.use("/", userRoutes); 25 | router.use("/", stateRoutes); 26 | router.use("/", touristCenterRoutes); 27 | router.use("/", newsletterRoutes); 28 | router.use("/", resetPasswordRoutes); 29 | router.use("/", musicRoutes); 30 | router.use("/", foodRoutes); 31 | router.use("/", historicalFactsRoutes); 32 | router.use("/", commentRoutes); 33 | 34 | export default router; 35 | -------------------------------------------------------------------------------- /src/routes/musicRoutes.js: -------------------------------------------------------------------------------- 1 | import Router from "express"; 2 | import musicController from "../controllers/music"; 3 | import Authentication from "../middlewares/authenticate"; 4 | 5 | // eslint-disable-next-line max-len 6 | const { 7 | getAllMusic, getMusic, addMusic, updateMusic, deleteMusic 8 | } = musicController; 9 | const { verifyAdmin, verifyToken } = Authentication; 10 | 11 | const router = Router(); 12 | 13 | router.get("/music/:id", getMusic); 14 | router.get("/music", getAllMusic); 15 | router.post("/admin/music/:countryId", verifyToken, verifyAdmin, addMusic); 16 | router.patch("/admin/music/:id", verifyToken, verifyAdmin, updateMusic); 17 | router.delete("/admin/music/:id", verifyToken, verifyAdmin, deleteMusic); 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /src/routes/newsletterRoute/newsletterRoutes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import Newsletters from "../../controllers/newsletter/newsletter"; 3 | import subscriber from "../../controllers/newsletter/subscriber"; 4 | import Authentication from "../../middlewares/authenticate"; 5 | 6 | const router = Router(); 7 | const { createNewsletter } = Newsletters; 8 | const { verifyAdmin, verifyToken } = Authentication; 9 | router.get("/newsletter/subscribers", subscriber.allSubscribers); 10 | router.get("/subscriber/verify/:email", subscriber.verifySubscriber); 11 | router.post("/newsletter/subscribe", subscriber.createSubscriber); 12 | router.get("/newsletter/unsubscribe/:email", subscriber.unsubscribe); 13 | router.post("/newsletter/admin/create_newsletter", verifyToken, verifyAdmin, createNewsletter); 14 | 15 | export default router; 16 | -------------------------------------------------------------------------------- /src/routes/resetPasswordRoutes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import resetPasswordController from "../controllers/resetPasswordController"; 3 | import Authentication from "../middlewares/authenticate"; 4 | 5 | const { verifyUserByDetails } = Authentication; 6 | 7 | const router = Router(); 8 | router.post("/users/recover", verifyUserByDetails, resetPasswordController.recover); 9 | router.post("/users/reset/:id/:token", resetPasswordController.reset); 10 | 11 | export default router; 12 | -------------------------------------------------------------------------------- /src/routes/stateRoutes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import AdminStateController from "../controllers/state"; 3 | import Authentication from "../middlewares/authenticate"; 4 | 5 | const { 6 | listStates, getState, addState, deleteState, updateState 7 | } = AdminStateController; 8 | const { verifyAdmin, verifyToken } = Authentication; 9 | 10 | const router = Router(); 11 | 12 | router.get("/states", listStates); 13 | router.get("/state/:id", getState); 14 | router.delete("/admin/state/:id", verifyToken, verifyAdmin, deleteState); 15 | router.patch("/admin/state/:id", verifyToken, verifyAdmin, updateState); 16 | router.post("/admin/state/:countryId", verifyToken, verifyAdmin, addState); 17 | 18 | export default router; 19 | -------------------------------------------------------------------------------- /src/routes/touristCenterRoute/touristCenterRoutes.js: -------------------------------------------------------------------------------- 1 | import Router from "express"; 2 | import touristCenterController from "../../controllers/touristCenter/touristCenter"; 3 | import Authentication from "../../middlewares/authenticate"; 4 | 5 | // eslint-disable-next-line max-len 6 | const { 7 | getAllTouristCenters, getTouristCenter, addTouristCenter, updateTouristCenter, deleteTouristCenter 8 | } = touristCenterController; 9 | const { verifyAdmin, verifyToken } = Authentication; 10 | 11 | const router = Router(); 12 | 13 | router.post("/admin/tourist-center/:countryId", verifyToken, verifyAdmin, addTouristCenter); 14 | router.get("/tourist-center/:id", getTouristCenter); 15 | router.get("/tourist-centers", getAllTouristCenters); 16 | router.patch("/admin/tourist-center/:id", verifyToken, verifyAdmin, updateTouristCenter); 17 | router.delete("/admin/tourist-center/:id", verifyToken, verifyAdmin, deleteTouristCenter); 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /src/routes/userRoute/userRoutes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import passport from "passport"; 3 | import userController from "../../controllers/user/user"; 4 | import Authentication from "../../middlewares/authenticate"; 5 | 6 | const router = Router(); 7 | const { verifyToken, verifyUserById } = Authentication; 8 | 9 | router.post("/users/signup", userController.createUser); 10 | router.get( 11 | "/auth/google/signup", 12 | passport.authenticate("googleSignUp", { 13 | scope: ["profile", "email"], 14 | }) 15 | ); 16 | 17 | router.get( 18 | "/auth/google/signin", 19 | passport.authenticate("googleSignIn", { 20 | scope: ["profile", "email"], 21 | }) 22 | ); 23 | router.patch("/user-profile/", verifyToken, verifyUserById, userController.updateUserProfile); 24 | router.get("/users/signup/verify/:email", userController.verifyUser); 25 | router.post("/users/signin", userController.loginUser); 26 | 27 | export default router; 28 | -------------------------------------------------------------------------------- /src/services/AdminServices/activateUser.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class User 5 | * @description User services 6 | * @exports User 7 | */ 8 | export default class User { 9 | /** 10 | * @param {string} id - The user id 11 | * @returns {object} - An instance of the Users model class 12 | */ 13 | static async activateUser(id) { 14 | try { 15 | return await database.Users.update({ 16 | active: true 17 | }, { 18 | where: { 19 | id 20 | }, 21 | returning: true, 22 | plain: true 23 | }); 24 | } catch (error) { 25 | throw error; 26 | } 27 | } 28 | 29 | /** 30 | * @param {string} id - The user id 31 | * @returns {object} - An instance of the Users model class 32 | */ 33 | static async deActivateUser(id) { 34 | try { 35 | return await database.Users.update({ 36 | active: false 37 | }, { 38 | where: { 39 | id 40 | }, 41 | returning: true, 42 | plain: true 43 | }); 44 | } catch (error) { 45 | throw error; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/services/AdminServices/countryService.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class Admin 5 | * @description allows admin user create and check country details 6 | * @exports Admin 7 | */ 8 | export default class Admin { 9 | /** 10 | * @param {string} newCountry - The country details 11 | * @returns {object} An instance of the Countries model class 12 | */ 13 | static async addCountry(newCountry) { 14 | try { 15 | return await database.Countries.create(newCountry); 16 | } catch (err) { 17 | throw err; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} countryName - The country name 23 | * @returns {object} An instance of the Countries model class 24 | */ 25 | static async checkCountry(countryName) { 26 | try { 27 | return await database.Countries.findOne({ where: { nameOfCountry: countryName } }); 28 | } catch (err) { 29 | throw err; 30 | } 31 | } 32 | 33 | /** 34 | * @param {uuid} countryId - The country name 35 | * @returns {object} An instance of the Countries model class 36 | */ 37 | static async checkCountryById(countryId) { 38 | try { 39 | return await database.Countries.findOne({ where: { id: countryId } }); 40 | } catch (err) { 41 | throw err; 42 | } 43 | } 44 | 45 | /** 46 | * @param {string} nameOfCountry - Native country of a food 47 | * @returns {object} - An instance of the Countries' model class 48 | */ 49 | static async countryName(nameOfCountry) { 50 | const renameCountry = nameOfCountry.split(" "); 51 | let newCountryName = []; 52 | for (let x = 0; x < renameCountry.length; x++) { 53 | newCountryName.push(renameCountry[x].charAt(0).toUpperCase() + renameCountry[x].slice(1)); 54 | } 55 | return newCountryName.join(" "); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/services/AdminServices/ethnicgroup.js: -------------------------------------------------------------------------------- 1 | import db from "../../models/index"; 2 | 3 | /** 4 | * @class EthnicGroups 5 | * @description allows admin user create and check EthnicGroup details 6 | * @exports EthnicGroups 7 | */ 8 | export default class EthnicGroups { 9 | /** 10 | * @param {string} newEthnicGroup - The EthnicGroup details 11 | * @returns {object} An instance of the EthnicGroup model class 12 | */ 13 | static async addEthnicGroup(newEthnicGroup) { 14 | try { 15 | return await db.EthnicGroups.create(newEthnicGroup); 16 | } catch (err) { 17 | throw err; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} id - EthnicGroup id 23 | * @param {string} ethnicGroup - EthnicGroup object 24 | * @returns {object} An updated instance of the EthnicGroup model class 25 | */ 26 | static async updateEthnicGroupById(id, ethnicGroup) { 27 | try { 28 | return await db.EthnicGroups.update(ethnicGroup, { 29 | where: { id }, 30 | returning: true, 31 | plain: true 32 | }); 33 | } catch (err) { 34 | throw err; 35 | } 36 | } 37 | 38 | /** 39 | * @param {string} name - The name 40 | * @returns {object} An instance of the EthnicGroups model class 41 | */ 42 | static async checkEthnicGroup(name) { 43 | try { 44 | return await db.EthnicGroups.findOne({ where: { name } }); 45 | } catch (err) { 46 | throw err; 47 | } 48 | } 49 | 50 | /** 51 | * @returns {object} An instance of the EthnicGroups model class 52 | */ 53 | static async getAllEthnicGroups() { 54 | try { 55 | return await db.EthnicGroups.findAll({ include: [{ model: db.Comments, as: "comments" }] }); 56 | } catch (error) { 57 | throw error; 58 | } 59 | } 60 | 61 | /** 62 | * @param {string} id - EthnicGroup id 63 | * @returns {object} An instance of the EthnicGroups model class 64 | */ 65 | static async findEthnicGroupById(id) { 66 | try { 67 | return await db.EthnicGroups.findOne({ 68 | where: { 69 | id, 70 | }, 71 | include: [{ model: db.Comments, as: "comments" }] 72 | }); 73 | } catch (err) { 74 | throw err; 75 | } 76 | } 77 | 78 | /** 79 | * @param {string} id - EthnicGroups object 80 | * @returns {object} An instance of the EthnicGroups model class 81 | */ 82 | static async deleteEthnicGroupById(id) { 83 | try { 84 | await db.EthnicGroups.destroy({ 85 | where: { 86 | id, 87 | } 88 | }); 89 | } catch (err) { 90 | throw err; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/services/AdminServices/historicalFactsService.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class Admin 5 | * @description allows admin user create and check country details 6 | * @exports Admin 7 | */ 8 | export default class HF_Services { 9 | /** 10 | * @param {string} newHistoricalFact - The HistoricalFacts details 11 | * @returns {object} An instance of the HistoricalFacts model class 12 | */ 13 | static async addHistoricalFact(newHistoricalFact) { 14 | try { 15 | return await database.Historicalfacts.create(newHistoricalFact); 16 | } catch (err) { 17 | throw err; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} location - Historicalfacts location 23 | * @returns {object} An instance of the Historicalfacts model class 24 | */ 25 | static async findHistoricalFactByLocation(location) { 26 | try { 27 | return await database.Historicalfacts.findAll({ 28 | where: { 29 | location, 30 | }, 31 | include: [{ model: database.Comments, as: "comments" }] 32 | }); 33 | } catch (err) { 34 | throw err; 35 | } 36 | } 37 | 38 | /** 39 | * @param {string} id - Historicalfacts id 40 | * @returns {object} An instance of the Historicalfacts model class 41 | */ 42 | static async findCountry(id) { 43 | try { 44 | return await database.Countries.findOne({ 45 | where: { 46 | id, 47 | }, 48 | }); 49 | } catch (err) { 50 | throw err; 51 | } 52 | } 53 | 54 | /** 55 | * @returns {object} All instances of the HistoricalFacts model class 56 | */ 57 | static async listHistoricalFacts() { 58 | try { 59 | return await database.Historicalfacts.findAll({ 60 | include: [{ model: database.Comments, as: "comments" }] 61 | }); 62 | } catch (err) { 63 | throw err; 64 | } 65 | } 66 | 67 | /** 68 | * @param {string} id - HistoricalFacts id 69 | * @returns {object} An instance of the HistoricalFacts model class 70 | */ 71 | static async findHistoricalFactById(id) { 72 | try { 73 | return await database.Historicalfacts.findOne({ 74 | where: { 75 | id, 76 | }, 77 | include: [{ model: database.Comments, as: "comments" }] 78 | }); 79 | } catch (err) { 80 | throw err; 81 | } 82 | } 83 | 84 | /** 85 | * @param {string} id - HistoricalFacts id 86 | * @param {string} newHistoricalFact - HistoricalFacts object 87 | * @returns {object} An updated instance of the HistoricalFacts model class 88 | */ 89 | static async editHistoricalFact(id, newHistoricalFact) { 90 | try { 91 | return await database.Historicalfacts.update(newHistoricalFact, { 92 | where: { id }, 93 | returning: true, 94 | plain: true 95 | }); 96 | } catch (err) { 97 | throw err; 98 | } 99 | } 100 | 101 | // eslint-disable-next-line valid-jsdoc 102 | /** 103 | * @param {string} id - HistoricalFacts object 104 | */ 105 | static async delHistoricalFact(id) { 106 | try { 107 | await database.Historicalfacts.destroy({ 108 | where: { 109 | id, 110 | } 111 | }); 112 | } catch (err) { 113 | throw err; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/services/AdminServices/musicService.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class Admin 5 | * @description allows admin user create and check country details 6 | * @exports Admin 7 | */ 8 | export default class db { 9 | /** 10 | * @param {string} newMusic - The Music details 11 | * @returns {object} An instance of the Music model class 12 | */ 13 | static async addMusic(newMusic) { 14 | try { 15 | return await database.Music.create(newMusic); 16 | } catch (err) { 17 | throw err; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} category - Music category 23 | * @returns {object} An instance of the Music model class 24 | */ 25 | static async findMusicByCategory(category) { 26 | try { 27 | return await database.Music.findOne({ 28 | where: { 29 | category, 30 | }, 31 | include: [{ model: database.Comments, as: "comments" }] 32 | }); 33 | } catch (err) { 34 | throw err; 35 | } 36 | } 37 | 38 | /** 39 | * @param {string} id - Music id 40 | * @returns {object} An instance of the Music model class 41 | */ 42 | static async findCountry(id) { 43 | try { 44 | return await database.Countries.findOne({ 45 | where: { 46 | id, 47 | } 48 | }); 49 | } catch (err) { 50 | throw err; 51 | } 52 | } 53 | 54 | /** 55 | * @returns {object} All instances of the Music model class 56 | */ 57 | static async listMusic() { 58 | try { 59 | return await database.Music.findAll({ include: [{ model: database.Comments, as: "comments" }] }); 60 | } catch (err) { 61 | throw err; 62 | } 63 | } 64 | 65 | /** 66 | * @param {string} id - Music id 67 | * @returns {object} An instance of the Music model class 68 | */ 69 | static async findMusicById(id) { 70 | try { 71 | return await database.Music.findOne({ 72 | where: { 73 | id, 74 | }, 75 | include: [{ model: database.Comments, as: "comments" }] 76 | }); 77 | } catch (err) { 78 | throw err; 79 | } 80 | } 81 | 82 | /** 83 | * @param {string} oldMusic - former Music object 84 | * @param {string} newMusic - new Music object 85 | * @returns {object} An updated instance of the Music model class 86 | */ 87 | static async editMusic(oldMusic, newMusic) { 88 | try { 89 | if (newMusic.gallery) { 90 | oldMusic.gallery.push(newMusic.gallery); 91 | // eslint-disable-next-line no-param-reassign 92 | newMusic.gallery = oldMusic.gallery; 93 | } 94 | return await database.Music.update(newMusic, { 95 | where: { id: oldMusic.id }, 96 | returning: true, 97 | plain: true 98 | }); 99 | } catch (err) { 100 | throw err; 101 | } 102 | } 103 | 104 | // eslint-disable-next-line valid-jsdoc 105 | /** 106 | * @param {string} id - Music object 107 | */ 108 | static async delMusic(id) { 109 | try { 110 | await database.Music.destroy({ 111 | where: { 112 | id, 113 | } 114 | }); 115 | } catch (err) { 116 | throw err; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/services/AdminServices/stateService.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class Admin 5 | * @description allows admin user create and check country details 6 | * @exports Admin 7 | */ 8 | export default class Admin { 9 | /** 10 | * @param {string} newState - The state details 11 | * @returns {object} An instance of the States model class 12 | */ 13 | static async addState(newState) { 14 | try { 15 | return await database.States.create(newState); 16 | } catch (err) { 17 | throw err; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} stateName - The state name 23 | * @returns {object} An instance of the States model class 24 | */ 25 | static async checkState(stateName) { 26 | try { 27 | const stringState = String(stateName); 28 | const Name = stringState[0].toUpperCase() + stringState.slice(1).toLowerCase(); 29 | return await database.States.findOne({ where: { name: Name } }); 30 | } catch (err) { 31 | throw err; 32 | } 33 | } 34 | 35 | /** 36 | * @param {string} countryid - The country id details 37 | * @returns {object} An instance of the States country class 38 | */ 39 | static async checkCountryId(countryid) { 40 | try { 41 | return await database.Countries.findOne({ where: { id: countryid } }); 42 | } catch (err) { 43 | throw err; 44 | } 45 | } 46 | 47 | /** 48 | * @returns {object} An instance of the States model class 49 | */ 50 | static async getAllStates() { 51 | try { 52 | return await database.States.findAll({ attributes: ["id", "name", "countryId", "capital", "gallery"], include: [{ model: database.Comments, as: "comments" }] }); 53 | } catch (err) { 54 | throw err; 55 | } 56 | } 57 | 58 | /** 59 | * @param {string} id - The state id 60 | * @returns {object} An instance of the States model class 61 | */ 62 | static async getState(id) { 63 | try { 64 | return await database.States.findOne({ 65 | where: { 66 | id 67 | }, 68 | include: [{ model: database.Comments, as: "comments" }] 69 | }); 70 | } catch (err) { 71 | throw err; 72 | } 73 | } 74 | 75 | /** 76 | * @param {string} id - The state name 77 | * @returns {object} An instance of the States model class 78 | */ 79 | static async deleteState(id) { 80 | try { 81 | const state = await database.States.findOne({ where: { id } }); 82 | return await state.destroy({ cascade: true }); 83 | } catch (err) { 84 | throw err; 85 | } 86 | } 87 | 88 | /** 89 | * @param {string} id - The old state name 90 | * @param {string} state - The new state details 91 | * @returns {object} An instance of the States model class 92 | */ 93 | static async updateState(id, state) { 94 | try { 95 | return await database.States.update(state, { 96 | where: { id }, 97 | returning: true, 98 | plain: true 99 | }); 100 | } catch (err) { 101 | throw err; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/services/AdminServices/touristCenterService.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class Admin 5 | * @description allows admin user create and check country details 6 | * @exports Admin 7 | */ 8 | export default class db { 9 | /** 10 | * @param {string} newTouristCenter - The TouristCenter details 11 | * @returns {object} An instance of the TouristCenters model class 12 | */ 13 | static async addTouristCenter(newTouristCenter) { 14 | try { 15 | return await database.TouristCenters.create(newTouristCenter); 16 | } catch (err) { 17 | throw err; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} name - TouristCenter name 23 | * @returns {object} An instance of the TouristCenter model class 24 | */ 25 | static async findTouristCenter(name) { 26 | try { 27 | return await database.TouristCenters.findOne({ 28 | where: { 29 | name, 30 | }, 31 | include: [{ model: database.Comments, as: "comments" }] 32 | }); 33 | } catch (err) { 34 | throw err; 35 | } 36 | } 37 | 38 | /** 39 | * @param {string} id - TouristCenter id 40 | * @returns {object} An instance of the TouristCenter model class 41 | */ 42 | static async findCountry(id) { 43 | try { 44 | return await database.Countries.findOne({ 45 | where: { 46 | id, 47 | } 48 | }); 49 | } catch (err) { 50 | throw err; 51 | } 52 | } 53 | 54 | /** 55 | * @returns {object} All instances of the TouristCenter model class 56 | */ 57 | static async listTouristCenters() { 58 | try { 59 | return await database.TouristCenters.findAll({ 60 | include: [{ model: database.Comments, as: "comments" }] 61 | }); 62 | } catch (err) { 63 | throw err; 64 | } 65 | } 66 | 67 | /** 68 | * @param {string} id - TouristCenter id 69 | * @returns {object} An instance of the TouristCenter model class 70 | */ 71 | static async findTouristCenterById(id) { 72 | try { 73 | return await database.TouristCenters.findOne({ 74 | where: { 75 | id, 76 | }, 77 | include: [{ model: database.Comments, as: "comments" }] 78 | }); 79 | } catch (err) { 80 | throw err; 81 | } 82 | } 83 | 84 | /** 85 | * @param {string} id - TouristCenter id 86 | * @param {string} touristCenter - TouristCenter object 87 | * @returns {object} An updated instance of the TouristCenter model class 88 | */ 89 | static async editTouristCenter(id, touristCenter) { 90 | try { 91 | return await database.TouristCenters.update(touristCenter, { 92 | where: { id }, 93 | returning: true, 94 | plain: true 95 | }); 96 | } catch (err) { 97 | throw err; 98 | } 99 | } 100 | 101 | // eslint-disable-next-line valid-jsdoc 102 | /** 103 | * @param {string} id - TouristCenter object 104 | */ 105 | static async delTouristCenter(id) { 106 | try { 107 | await database.TouristCenters.destroy({ 108 | where: { 109 | id, 110 | } 111 | }); 112 | } catch (err) { 113 | throw err; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/services/UserService/User.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class User 5 | * @description User services 6 | * @exports User 7 | */ 8 | export default class User { 9 | /** 10 | * @param {string} username - The user name 11 | * @returns {object} - An instance of the Users model class 12 | */ 13 | static async usernameExist(username) { 14 | try { 15 | const usernameExist = await database.Users.findOne({ 16 | where: { 17 | username 18 | } 19 | }); 20 | return usernameExist; 21 | } catch (error) { 22 | throw error; 23 | } 24 | } 25 | 26 | /** 27 | * @param {string} email - The user email 28 | * @returns {object} - An instance of the Users model class 29 | */ 30 | static async emailExist(email) { 31 | try { 32 | return await database.Users.findOne({ 33 | where: { 34 | email 35 | } 36 | }); 37 | } catch (error) { 38 | throw error; 39 | } 40 | } 41 | 42 | /** 43 | * @param {object} newUser - The user details 44 | * @returns {object} - An instance of the Users model class 45 | */ 46 | static async createUser(newUser) { 47 | try { 48 | const createUser = await database.Users.create(newUser); 49 | const userToUpdate = await database.Users.findOne({ 50 | where: { 51 | id: createUser.id 52 | } 53 | }); 54 | if (userToUpdate) { 55 | const newProfile = { 56 | userId: userToUpdate.id 57 | }; 58 | await database.Profiles.create(newProfile); 59 | return createUser; 60 | } 61 | } catch (error) { 62 | throw error; 63 | } 64 | } 65 | 66 | /** 67 | * @param {string} email - The user email 68 | * @returns {object} - An instance of the Users model class 69 | */ 70 | static async updateUserVerification(email) { 71 | try { 72 | return await database.Users.update({ 73 | verified: true 74 | }, { 75 | where: { 76 | email 77 | }, 78 | returning: true, 79 | plain: true 80 | }); 81 | } catch (error) { 82 | throw error; 83 | } 84 | } 85 | 86 | /** 87 | * @param {string} id - The user id 88 | * @param {string} profile - The user profile details 89 | * @returns {object} - An instance of the Profile model class 90 | */ 91 | static async updateUserProfile(id, profile) { 92 | try { 93 | return await database.Profiles.update(profile, { 94 | where: { userId: id }, 95 | returning: true, 96 | plain: true 97 | }); 98 | } catch (error) { 99 | throw error; 100 | } 101 | } 102 | 103 | /** 104 | * @param {string} id - The user id 105 | * @returns {object} - An instance of the Users model class 106 | */ 107 | static async findUser(id) { 108 | try { 109 | return await database.Users.findOne({ where: { id } }); 110 | } catch (error) { 111 | throw error; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/services/commentServices/index.js: -------------------------------------------------------------------------------- 1 | import db from "../../models"; 2 | 3 | const commentServices = { 4 | addComment: async commentData => { 5 | try { 6 | return await db.Comments.create(commentData); 7 | } catch (error) { 8 | throw error; 9 | } 10 | }, 11 | getComment: async id => { 12 | try { 13 | return await db.Comments.findOne({ where: { id } }); 14 | } catch (error) { 15 | throw error; 16 | } 17 | }, 18 | getAllComments: async id => { 19 | try { 20 | return await db.Comments.findAll({ 21 | where: { 22 | userId: id 23 | }, 24 | order: [ 25 | ["createdAt", "DESC"], 26 | ], 27 | }); 28 | } catch (error) { 29 | throw error; 30 | } 31 | }, 32 | }; 33 | 34 | export default commentServices; 35 | -------------------------------------------------------------------------------- /src/services/foodServices/food.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class Food 5 | * @description Food services 6 | * @exports Food 7 | */ 8 | export default class FoodServices { 9 | /** 10 | * @param {string} foodDetails - Details used to create a food 11 | * @returns {object} - An instance of the Foods model class 12 | */ 13 | static async addFood(foodDetails) { 14 | try { 15 | return await database.Foods.create(foodDetails); 16 | } catch (error) { 17 | return error; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} id - Id of foods 23 | * @returns {object} - An instance of the Foods model class 24 | */ 25 | static async getOneFoodById(id) { 26 | try { 27 | return await database.Foods.findOne({ 28 | where: { id }, include: [{ model: database.Comments, as: "comments" }] 29 | }); 30 | } catch (error) { 31 | return error; 32 | } 33 | } 34 | 35 | /** 36 | * @param {string} id - Id of foods 37 | * @param {string} foodDetails - Details with which to update specific food 38 | * @returns {object} - An instance of the Foods model class 39 | */ 40 | static async updateFood(id, foodDetails) { 41 | try { 42 | const findFood = await database.Foods.findOne({ 43 | where: { id } 44 | }); 45 | if (findFood) { 46 | return await database.Foods.update(foodDetails, 47 | { 48 | where: { id: findFood.id }, 49 | returning: true, 50 | plain: true 51 | }); 52 | } 53 | } catch (error) { 54 | return error; 55 | } 56 | } 57 | 58 | /** 59 | * @param {string} getFood - Details of food to delete 60 | * @returns {object} - An instance of the Foods model class 61 | */ 62 | static async deleteFood(getFood) { 63 | try { 64 | return database.Foods.destroy({ 65 | getFood, 66 | where: { id: getFood.id } 67 | }); 68 | } catch (error) { 69 | throw error; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/services/newsletterServices/newsletter.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class Newsletter 5 | * @description Newsletter services 6 | * @exports Newsletter 7 | */ 8 | export default class Newsletter { 9 | /** 10 | * @param {string} newsletterDetails - Details used to create a newsletter 11 | * @returns {object} - An instance of the Newsletters model class 12 | */ 13 | static async createNewsletter(newsletterDetails) { 14 | try { 15 | return await database.Newsletters.create(newsletterDetails); 16 | } catch (error) { 17 | throw error; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} subscriberId - ID of a registered subscriber 23 | * @param {string} newsletter_id - ID on the newsletter 24 | * @returns {object} - An instance of the Newsletters and Newsletter_Subscribers model classes 25 | */ 26 | static async updateNewsletterId(subscriberId, newsletter_id) { 27 | const newSub = await database.Newsletter_Subscribers.findOne({ 28 | where: { subscriberId } 29 | }); 30 | if (newSub) { 31 | newSub.newsletterId.push(newsletter_id); 32 | await database.Newsletter_Subscribers.update({ 33 | newsletterId: newSub.newsletterId 34 | }, { 35 | where: { subscriberId } 36 | }); 37 | } 38 | } 39 | 40 | /** 41 | * @param {string} title - Title of newsletter 42 | * @returns {object} - An instance of the Newsletters model class 43 | */ 44 | static async getNewsletter(title) { 45 | try { 46 | return await database.Newsletters.findOne({ 47 | where: { title }, 48 | }); 49 | } catch (error) { 50 | throw error; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/services/newsletterServices/subsciber.js: -------------------------------------------------------------------------------- 1 | import database from "../../models"; 2 | 3 | /** 4 | * @class Subscriber 5 | * @description Subscriber services 6 | * @exports Subscriber 7 | */ 8 | export default class Subscriber { 9 | /** 10 | * @param {string} email - The subscriber's email 11 | * @returns {object} - An instance of the Subscribers model class 12 | */ 13 | static async emailExist(email) { 14 | try { 15 | return await database.Subscribers.findOne({ where: { email } }); 16 | } catch (error) { 17 | throw error; 18 | } 19 | } 20 | 21 | /** 22 | * @param {string} subscriberDetails - Details used to create a new subscriber 23 | * @returns {object} - An instance of the Subscribers model class 24 | */ 25 | static async subscribe(subscriberDetails) { 26 | try { 27 | const createSubscriber = await database.Subscribers.create(subscriberDetails); 28 | const subscriber = await database.Subscribers.findOne( 29 | { 30 | where: { 31 | id: createSubscriber.id 32 | } 33 | } 34 | ); 35 | if (subscriber) { 36 | const subscriberId = { 37 | subscriberId: subscriber.id 38 | }; 39 | await database.Newsletter_Subscribers.create(subscriberId); 40 | } 41 | } catch (error) { 42 | throw error; 43 | } 44 | } 45 | 46 | /** 47 | * @param {string} email - The registered email of subscriber 48 | * @returns {object} - An instance of the Subscribers model class 49 | */ 50 | static async updateSubscriberVerification(email) { 51 | try { 52 | return await database.Subscribers.update({ 53 | verified: true 54 | }, { 55 | where: { 56 | email 57 | }, 58 | returning: true, 59 | plain: true 60 | }); 61 | } catch (error) { 62 | throw error; 63 | } 64 | } 65 | 66 | /** 67 | * @param {string} email - The registered email of subscriber 68 | * @param {string} title - Title of newsletter received by subscriber 69 | * @returns {object} - An instance of the Subscribers model class 70 | */ 71 | static async receivedMail(email, title) { 72 | try { 73 | const subscriber = await database.Subscribers.findOne({ 74 | where: { email } 75 | }); 76 | if (subscriber) { 77 | subscriber.newsletter.push(title); 78 | await database.Subscribers.update({ 79 | newsletter: subscriber.newsletter 80 | }, { 81 | where: { email }, 82 | }); 83 | } 84 | } catch (error) { 85 | throw error; 86 | } 87 | } 88 | 89 | /** 90 | * @returns {object} - An instance of the Subscribers model class 91 | */ 92 | static async subscribers() { 93 | try { 94 | const allSubscribers = await database.Subscribers.findAll(); 95 | return allSubscribers; 96 | } catch (error) { 97 | throw error; 98 | } 99 | } 100 | 101 | /** 102 | * @param {string} emailExist - The registered email of subscriber 103 | * @returns {object} - An instance of the Subscribers model class 104 | */ 105 | static async unsubscribe(emailExist) { 106 | try { 107 | if (emailExist) { 108 | const deleteSubscriber = await database.Subscribers.destroy({ 109 | emailExist, 110 | where: { email: emailExist.email } 111 | }); 112 | await database.Newsletter_Subscribers.destroy({ 113 | emailExist, 114 | where: { subscriberId: emailExist.id } 115 | }); 116 | return deleteSubscriber; 117 | } 118 | } catch (error) { 119 | throw error; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/tests/__mock__/commentMockData.js: -------------------------------------------------------------------------------- 1 | export default { 2 | comment: "I am a test comment", 3 | userId: "98e0350f-ed09-46b0-83d7-8a135afeaf84", 4 | }; 5 | -------------------------------------------------------------------------------- /src/tests/__mock__/countriesMockData.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: "030f7257-5fa4-4015-a1f0-b17408a11e30", 4 | nameOfCountry: "Nigeria", 5 | gallery: 6 | "https://img.freepik.com/free-vector/nigeria-map-flag-national-emblem_2239-230.jpg?size=338&ext=jpg", 7 | capital: "FCT Abuja", 8 | population: 205, 9 | officialLanguage: "English", 10 | region: "West Africa", 11 | currency: "Naira", 12 | createdAt: new Date(), 13 | updatedAt: new Date(), 14 | }, 15 | { 16 | id: "2e11e4a9-441b-4426-9521-39adc64ccfad", 17 | nameOfCountry: "Zambia", 18 | gallery: 19 | "https://cdn.pixabay.com/photo/2013/07/13/14/18/zambia-162464_960_720.png", 20 | capital: "Lusaka", 21 | population: 17351708, 22 | officialLanguage: "English", 23 | region: "Southern Africa", 24 | currency: "Kwacha", 25 | createdAt: new Date(), 26 | updatedAt: new Date(), 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /src/tests/__mock__/countryMockData.js: -------------------------------------------------------------------------------- 1 | export default 2 | { 3 | id: "030f7257-5fa4-4015-a1f0-b17408a11e30", 4 | nameOfCountry: "Nigeria", 5 | gallery: 6 | "https://img.freepik.com/free-vector/nigeria-map-flag-national-emblem_2239-230.jpg?size=338&ext=jpg", 7 | capital: "FCT Abuja", 8 | population: 205, 9 | officialLanguage: "English", 10 | region: "West Africa", 11 | currency: "Naira", 12 | createdAt: new Date(), 13 | updatedAt: new Date(), 14 | }; 15 | -------------------------------------------------------------------------------- /src/tests/controllers/admin/activateUser.test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import { user4, user7 } from "../users/user-sign-in-test-data"; 4 | import server from "../../../app"; 5 | import sendGrid from "../../../utilities/sendgrid"; 6 | 7 | sendGrid.sandboxMode(); 8 | chai.should(); 9 | 10 | const { expect } = chai; 11 | chai.use(chaiHttp); 12 | 13 | describe("Activate a user", () => { 14 | let adminToken; 15 | let userToken; 16 | before(done => { 17 | chai 18 | .request(server) 19 | .post("/api/v1/users/signin") 20 | .set("Accept", "application/json") 21 | .send(user4) 22 | .end((err, res) => { 23 | if (err) throw err; 24 | adminToken = res.body.token; 25 | done(); 26 | }); 27 | }); 28 | before(done => { 29 | chai 30 | .request(server) 31 | .post("/api/v1/users/signin") 32 | .set("Accept", "application/json") 33 | .send(user7) 34 | .end((err, res) => { 35 | if (err) throw err; 36 | userToken = res.body.token; 37 | done(); 38 | }); 39 | }); 40 | it("should allow user with admin role activate a user", done => { 41 | chai 42 | .request(server) 43 | .patch("/api/v1/admin/activate-user/fc1f4e85-8e83-4a38-ab1e-8e4da2c6dd25") 44 | .set("Authorization", `Bearer ${adminToken}`) 45 | .end((err, res) => { 46 | expect(res).to.have.status(200); 47 | expect(res.body.message).to.equal("User activated successfully!"); 48 | done(); 49 | }); 50 | }); 51 | it("should not allow user without token activate a user", done => { 52 | chai 53 | .request(server) 54 | .patch("/api/v1/activate-user/fc1f4e85-8e83-4a38-ab1e-8e4da2c6dd25") 55 | .end((err, res) => { 56 | expect(res).to.have.status(404); 57 | done(); 58 | }); 59 | }); 60 | it("should not allow user without admin role activate a user", done => { 61 | chai 62 | .request(server) 63 | .patch("/api/v1/activate-user/fc1f4e85-8e83-4a38-ab1e-8e4da2c6dd25") 64 | .set("Authorization", `Bearer ${userToken}`) 65 | .end((err, res) => { 66 | expect(res).to.have.status(404); 67 | done(); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/tests/controllers/admin/addCountry.test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import { user4, user5 } from "../users/user-sign-in-test-data"; 4 | import { country, country2, country3 } from "./addcountry-data"; 5 | import server from "../../../app"; 6 | import sendGrid from "../../../utilities/sendgrid"; 7 | 8 | sendGrid.sandboxMode(); 9 | chai.should(); 10 | 11 | const { expect } = chai; 12 | chai.use(chaiHttp); 13 | 14 | describe("Add country", () => { 15 | let adminToken; 16 | let userToken; 17 | before(done => { 18 | chai 19 | .request(server) 20 | .post("/api/v1/users/signin") 21 | .set("Accept", "application/json") 22 | .send(user4) 23 | .end((err, res) => { 24 | if (err) throw err; 25 | adminToken = res.body.token; 26 | done(); 27 | }); 28 | }); 29 | before(done => { 30 | chai 31 | .request(server) 32 | .post("/api/v1/users/signin") 33 | .set("Accept", "application/json") 34 | .send(user5) 35 | .end((err, res) => { 36 | if (err) throw err; 37 | userToken = res.body.token; 38 | done(); 39 | }); 40 | }); 41 | it("should allow user with admin role add a country", done => { 42 | chai 43 | .request(server) 44 | .post("/api/v1/admin/country") 45 | .set("Authorization", `Bearer ${adminToken}`) 46 | .set("Accept", "application/json") 47 | .send(country) 48 | .end((err, res) => { 49 | expect(res).to.have.status(201); 50 | done(); 51 | }); 52 | }); 53 | it("should not allow admin add the same country twice", done => { 54 | chai 55 | .request(server) 56 | .post("/api/v1/admin/country") 57 | .set("Authorization", `Bearer ${adminToken}`) 58 | .set("Accept", "application/json") 59 | .send(country) 60 | .end((err, res) => { 61 | expect(res).to.have.status(409); 62 | done(); 63 | }); 64 | }); 65 | it("should not allow admin add a country with incomplete details", done => { 66 | chai 67 | .request(server) 68 | .post("/api/v1/admin/country") 69 | .set("Authorization", `Bearer ${adminToken}`) 70 | .set("Accept", "application/json") 71 | .send(country2) 72 | .end((err, res) => { 73 | expect(res).to.have.status(400); 74 | done(); 75 | }); 76 | }); 77 | it("should not allow user without token add a country", done => { 78 | chai 79 | .request(server) 80 | .post("/api/v1/admin/country") 81 | .send(country3) 82 | .end((err, res) => { 83 | expect(res).to.have.status(401); 84 | done(); 85 | }); 86 | }); 87 | it("should not allow user without admin role add a country", done => { 88 | chai 89 | .request(server) 90 | .post("/api/v1/admin/country") 91 | .set("Authorization", `Bearer ${userToken}`) 92 | .set("Accept", "application/json") 93 | .send(country3) 94 | .end((err, res) => { 95 | expect(res).to.have.status(403); 96 | done(); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /src/tests/controllers/admin/addState-data.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | name: "Anambra", 3 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 4 | capital: "Awka", 5 | gallery: "https://en.wikipedia.org/wiki/Lusaka#/media/File:Downtown_Lusaka.JPG", 6 | }; 7 | const state2 = { 8 | name: "Lagos", 9 | capital: "Ikeja" 10 | }; 11 | 12 | const state3 = { 13 | name: "Kano", 14 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 15 | capital: "Kano", 16 | gallery: "https://img.freepik.com/free-vector/nigeria-map-flag-national-emblem_2239-230.jpg?size=338&ext=jpg", 17 | }; 18 | export { state, state2, state3 }; 19 | -------------------------------------------------------------------------------- /src/tests/controllers/admin/addState.test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import { user4, user5 } from "../users/user-sign-in-test-data"; 4 | import { state, state2, state3 } from "./addState-data"; 5 | import server from "../../../app"; 6 | 7 | chai.should(); 8 | 9 | const { expect } = chai; 10 | chai.use(chaiHttp); 11 | 12 | describe("Add state", () => { 13 | let adminToken; 14 | let userToken; 15 | before(done => { 16 | chai 17 | .request(server) 18 | .post("/api/v1/users/signin") 19 | .set("Accept", "application/json") 20 | .send(user4) 21 | .end((err, res) => { 22 | if (err) throw err; 23 | adminToken = res.body.token; 24 | done(); 25 | }); 26 | }); 27 | before(done => { 28 | chai 29 | .request(server) 30 | .post("/api/v1/users/signin") 31 | .set("Accept", "application/json") 32 | .send(user5) 33 | .end((err, res) => { 34 | if (err) throw err; 35 | userToken = res.body.token; 36 | done(); 37 | }); 38 | }); 39 | it("should allow user with admin role add a state", done => { 40 | chai 41 | .request(server) 42 | .post("/api/v1/admin/state") 43 | .set("Authorization", `Bearer ${adminToken}`) 44 | .set("Accept", "application/json") 45 | .send(state) 46 | .end((err, res) => { 47 | expect(res).to.have.status(201); 48 | done(); 49 | }); 50 | }); 51 | it("should not allow admin add the same state twice", done => { 52 | chai 53 | .request(server) 54 | .post("/api/v1/admin/state") 55 | .set("Authorization", `Bearer ${adminToken}`) 56 | .set("Accept", "application/json") 57 | .send(state) 58 | .end((err, res) => { 59 | expect(res).to.have.status(409); 60 | done(); 61 | }); 62 | }); 63 | it("should not allow admin add a state with incomplete details", done => { 64 | chai 65 | .request(server) 66 | .post("/api/v1/admin/state") 67 | .set("Authorization", `Bearer ${adminToken}`) 68 | .set("Accept", "application/json") 69 | .send(state2) 70 | .end((err, res) => { 71 | expect(res).to.have.status(409); 72 | done(); 73 | }); 74 | }); 75 | it("should not allow user without token add a state", done => { 76 | chai 77 | .request(server) 78 | .post("/api/v1/admin/state") 79 | .send(state3) 80 | .end((err, res) => { 81 | expect(res).to.have.status(401); 82 | done(); 83 | }); 84 | }); 85 | it("should not allow user without admin role add a state", done => { 86 | chai 87 | .request(server) 88 | .post("/api/v1/admin/state") 89 | .set("Authorization", `Bearer ${userToken}`) 90 | .set("Accept", "application/json") 91 | .send(state3) 92 | .end((err, res) => { 93 | expect(res).to.have.status(403); 94 | done(); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/tests/controllers/admin/addcountry-data.js: -------------------------------------------------------------------------------- 1 | const country = { 2 | nameOfCountry: "Kenya", 3 | gallery: "https://img.freepik.com/free-vector/nigeria-map-flag-national-emblem_2239-230.jpg?size=338&ext=jpg", 4 | capital: "Nairobi", 5 | population: 205, 6 | officialLanguage: "English", 7 | region: "West Africa", 8 | currency: "Kenyan Shillings" 9 | }; 10 | const country2 = { 11 | nameOfCountry: "Zambia", 12 | gallery: "https://img.freepik.com/free-vector/nigeria-map-flag-national-emblem_2239-230.jpg?size=338&ext=jpg", 13 | capital: "Lusaka", 14 | population: 205, 15 | officialLanguage: "English", 16 | region: "West Africa" 17 | }; 18 | 19 | const country3 = { 20 | nameOfCountry: "Ghana", 21 | gallery: "https://img.freepik.com/free-vector/nigeria-map-flag-national-emblem_2239-230.jpg?size=338&ext=jpg", 22 | capital: "Accra", 23 | population: 205, 24 | officialLanguage: "English", 25 | region: "West Africa", 26 | currency: "Ghanaian cedi" 27 | }; 28 | export { country, country2, country3 }; 29 | -------------------------------------------------------------------------------- /src/tests/controllers/admin/deActivateUser.test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import { user4, user7 } from "../users/user-sign-in-test-data"; 4 | import server from "../../../app"; 5 | import sendGrid from "../../../utilities/sendgrid"; 6 | 7 | sendGrid.sandboxMode(); 8 | chai.should(); 9 | 10 | const { expect } = chai; 11 | chai.use(chaiHttp); 12 | 13 | describe("De-Activate a user", () => { 14 | let adminToken; 15 | let userToken; 16 | before(done => { 17 | chai 18 | .request(server) 19 | .post("/api/v1/users/signin") 20 | .set("Accept", "application/json") 21 | .send(user4) 22 | .end((err, res) => { 23 | if (err) throw err; 24 | adminToken = res.body.token; 25 | done(); 26 | }); 27 | }); 28 | before(done => { 29 | chai 30 | .request(server) 31 | .post("/api/v1/users/signin") 32 | .set("Accept", "application/json") 33 | .send(user7) 34 | .end((err, res) => { 35 | if (err) throw err; 36 | userToken = res.body.token; 37 | done(); 38 | }); 39 | }); 40 | it("should allow user with admin role De-activate a user", done => { 41 | chai 42 | .request(server) 43 | .patch("/api/v1/admin/deactivate-user/fc1f4e85-8e83-4a38-ab1e-8e4da2c6dd25") 44 | .set("Authorization", `Bearer ${adminToken}`) 45 | .set("Accept", "application/json") 46 | .end((err, res) => { 47 | expect(res).to.have.status(200); 48 | done(); 49 | }); 50 | }); 51 | it("should not allow user without token De-activate a user", done => { 52 | chai 53 | .request(server) 54 | .patch("/api/v1/deactivate-user/fc1f4e85-8e83-4a38-ab1e-8e4da2c6dd25") 55 | .end((err, res) => { 56 | expect(res).to.have.status(404); 57 | done(); 58 | }); 59 | }); 60 | it("should not allow user without admin role de-activate a user", done => { 61 | chai 62 | .request(server) 63 | .patch("/api/v1/deactivate-user/fc1f4e85-8e83-4a38-ab1e-8e4da2c6dd25") 64 | .set("Authorization", `Bearer ${userToken}`) 65 | .end((err, res) => { 66 | expect(res).to.have.status(404); 67 | done(); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/tests/controllers/ethnicGroup/ethnicGroup-data.js: -------------------------------------------------------------------------------- 1 | const ethnicGroup = { 2 | name: "Ibo", 3 | festivals: "New Yam Festival", 4 | dressing: "In the old days, some Igbo women celebrated their feast in ekwerike clothing. In other words, they tied thick fabric around their waist. Others preferred painting their bodies with ufle, nzu, edo, uri and other colored ornaments (instead of wearing clothing) and decorating their waist with bead accessories.", 5 | language: "Igbo", 6 | gallery: "https://unsplash.com/photos/eS_aZA5S42Y", 7 | culturalPractices: "The Igbo people have a traditional religious belief that there is one creator, called ‘Chineke’ or ‘Chukwu’. The creator can be approached through many other deities and spirits in the form of natural objects, most commonly through the god of thunder called ‘Amadioha’." 8 | }; 9 | const ethnicGroup2 = { 10 | name: "Bini", 11 | festivals: "New Yam Festival" 12 | }; 13 | 14 | const ethnicGroup3 = { 15 | id: "09015644-4195-417f-8934-7cdc6e8519e2", 16 | countryId: "2e11e4a9-441b-4426-9521-39adc64ccfad", 17 | name: "Bemba", 18 | festivals: "Ukusefya Pa Ngwena", 19 | dressing: "Before the arrival of Europeans, the most common type of cloth was made from bark. Women wore it around the waist as a loincloth. Today most Zambians, including the Bemba, wear modern clothes. Men wear Western clothing (shorts, pants, and shirts). However, the designs and fashions in women's dresses are usually of Zambian or African origin.", 20 | language: "Bemba", 21 | gallery: 22 | "https://unsplash.com/photos/eS_aZA5S42Y", 23 | culturalPractices: "When a man and women are married the man goes to live with the wife's family and so generations are traced in a matrilineal fashion, as opposed to the more common patrilineal lineages.", 24 | createdAt: new Date(), 25 | updatedAt: new Date(), 26 | }; 27 | 28 | const ethnicGroup4 = { 29 | id: "63995ef8-351f-4035-a268-c6cd7697f0ef", 30 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 31 | name: "Yoruba", 32 | festivals: "Eyo festival", 33 | dressing: "The most commonly worn by women are Ìró (wrapper) and Bùbá (blouse–like loose top). They also have matching Gèlè (head gear) that must be put on whenever the Ìró and Bùbá is on. Just as the cap (Fìlà) is important to men, women’s dressing is considered incomplete without Gèlè", 34 | language: "Yoruba", 35 | gallery: 36 | "https://unsplash.com/photos/eS_aZA5S42Y", 37 | culturalPractices: "Some Yoruba believe that a baby may come with pre-destined names. For instance, twins (ibeji) are believed to have natural-birth names. Thus the first to be born of the two is called Taiwo or 'Taiye', shortened forms of Taiyewo, meaning the taster of the world. This is to identify the first twin as the one sent by the other one to first go and taste the world. If he/she stays there, it follows that it is not bad, and that would send a signal to the other one to start coming. Hence the second to arrive is named Kehinde (late arrival; it is now common for many Kehindes to be called by the familiar diminutive 'Kenny'.Irrespective of the sex the child born to the same woman after the twins is called Idowu, and the one after this is called Alaba, then the next child is called Idogbe. The Yoruba believe that some children are born to die. This derives from the phenomenon of the tragic incidents of high rate of infant mortality sometimes afflicting the same family for a long time. When this occurs, the family devises all kinds of method to forestall a recurrence, including giving special names at a new birth.", 38 | createdAt: new Date(), 39 | updatedAt: new Date(), 40 | }; 41 | 42 | export { 43 | ethnicGroup, ethnicGroup2, ethnicGroup3, ethnicGroup4 44 | }; 45 | -------------------------------------------------------------------------------- /src/tests/controllers/food/food.data.js: -------------------------------------------------------------------------------- 1 | const food = { 2 | foodName: "Smoked Indomie", 3 | methodOfPreparation: "Avoid packaged or processed foods, which are likely to contain added salt, sugar and fats. Recognize that consuming these foods increases your intake of salt, sugar, and fats considerably (often without knowing specifically what or how much). As we eat more and more processed foods, we eat less of the phytochemicals and nutrients our bodies need. ", 4 | gallery: "htpp://facebook.com" 5 | }; 6 | 7 | const food2 = { 8 | foodName: "smoked indomie", 9 | methodOfPreparation: "Avoid packaged or processed foods, which are likely to contain added salt, sugar and fats. Recognize that consuming these foods increases your intake of salt, sugar, and fats considerably (often without knowing specifically what or how much). As we eat more and more processed foods, we eat less of the phytochemicals and nutrients our bodies need. ", 10 | gallery: "htpp://facebook.com" 11 | }; 12 | 13 | const food3 = { 14 | foodName: "Egusi Stew", 15 | methodOfPreparation: "Add melon, onion, water and fresh pepper in a blender. Blend the ingredients together until it forms a paste. Add water, chicken, turkey, ponmo, yellow pepper, stock, smoked panla fish, crayfish, chopped pepper and palm oil to a pot. Allow all the ingredients to boil for 15 minutes. Now scoop little bits of the egusi paste into the pot. Do not stir in the mixture, just cover the pot and allow it to cook for 10 minutes. Next add the Ugu leaves and Uziza, mix together and allow to cook for 5 minutes. You will notice that the oil will start to rise to the top, your Egusi Soup is now ready. Serve and enjoy with Eba, Semo, Pounded Yam or even Rice.", 16 | gallery: "https://images.unsplash.com/photo-1484723091739-30a097e8f929?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" 17 | 18 | }; 19 | 20 | export { 21 | food, 22 | food2, 23 | food3, 24 | }; 25 | -------------------------------------------------------------------------------- /src/tests/controllers/music/music-data.js: -------------------------------------------------------------------------------- 1 | const music = { 2 | category: "Rap", 3 | gallery: "https://res.cloudinary.com/augustar/image/upload/v1599565560/music_cl7glf.jpg", 4 | information: "Rapping is a musical form of vocal delivery that incorporates 'rhyme, rhythmic speech, and street vernacular', which is performed or chanted in a variety of ways, usually over a backing beat or musical accompaniment. The components of rap include content and flow" 5 | }; 6 | const music2 = { 7 | category: "Fuji", 8 | }; 9 | 10 | const music3 = { 11 | category: "Juju", 12 | gallery: "https://res.cloudinary.com/augustar/image/upload/v1599565560/music_cl7glf.jpg", 13 | information: "Jùjú is a style of Nigerian popular music, derived from traditional Yoruba percussion. The name comes from the Yoruba word 'juju' or 'jiju' meaning 'throwing' or 'something being thrown'" 14 | }; 15 | 16 | const music4 = { 17 | id: "ea26d3c7-4635-436f-ab2e-a463792301c9", 18 | countryId: "2e11e4a9-441b-4426-9521-39adc64ccfad", 19 | category: "Highlife", 20 | information: "Highlife is a music genre that originated in present-day Ghana early in the 20th century, during its history as a colony of the British Empire. It uses the melodic and main rhythmic structures of traditional Akan music, but is played with Western instruments.", 21 | gallery: ["https://res.cloudinary.com/augustar/image/upload/v1599565560/music_cl7glf.jpg"], 22 | createdAt: new Date(), 23 | updatedAt: new Date(), 24 | }; 25 | 26 | const music5 = { 27 | id: "1e09a076-52cb-4597-94f4-8f106a3db4d3", 28 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 29 | category: "Soul", 30 | information: "Soul music is a popular music genre that originated in the African American community throughout the United States in the 1950s and early 1960s. It combines elements of African-American gospel music, rhythm and blues and jazz", 31 | gallery: ["https://res.cloudinary.com/augustar/image/upload/v1599565560/music_cl7glf.jpg"], 32 | createdAt: new Date(), 33 | updatedAt: new Date(), 34 | }; 35 | 36 | // eslint-disable-next-line object-curly-newline 37 | export { music, music2, music3, music4, music5 }; 38 | -------------------------------------------------------------------------------- /src/tests/controllers/newsletter/newsletter-test-data.js: -------------------------------------------------------------------------------- 1 | const subscriber1 = { 2 | firstName: "John", 3 | email: "john@gmail.com" 4 | }; 5 | 6 | const subscriber2 = { 7 | email: "garry@gmail.com" 8 | }; 9 | 10 | const subscriber3 = { 11 | firstName: "ObiOfNairobi", 12 | email: "john@gmail.com" 13 | }; 14 | 15 | const subscriber4 = { 16 | email: "john@gmail.com" 17 | }; 18 | 19 | const newsletter1 = { 20 | title: "Welcome To Africa", 21 | message: "Africa is beautiful. Africa is bold. Africa is Home to beautiful and resilient people. Welcome to Africa" 22 | }; 23 | 24 | const newsletter2 = { 25 | message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vestibulum lacinia nunc dapibus malesuada. Suspendisse ut ligula id velit posuere tincidunt. Sed consectetur id purus a placerat. In vel bibendum ipsum. Nunc purus ex, interdum a sodales ut, lobortis nec quam. Sed cursus vehicula massa in dignissim. In libero metus, posuere et libero sed, eleifend bibendum arcu. Aliquam cursus nibh magna, sed euismod diam tristique in. Mauris nisl neque, imperdiet vitae eros et, efficitur finibus lorem. Pellentesque a mollis ex." 26 | }; 27 | 28 | export { 29 | newsletter2, 30 | newsletter1, 31 | subscriber4, 32 | subscriber3, 33 | subscriber2, 34 | subscriber1 35 | }; 36 | -------------------------------------------------------------------------------- /src/tests/controllers/resetPasswordTest/reset-test-data.js: -------------------------------------------------------------------------------- 1 | import signToken from "../../../utilities/signToken"; 2 | 3 | const user = { 4 | id: "80e13d8c-b0aa-431f-a107-8aad5bd9324c", 5 | username: "Billy", 6 | email: "balldash@live.com", 7 | password: "thunderbolt", 8 | toJSON: () => ({ 9 | id: "80e13d8c-b0aa-431f-a107-8aad5bd9324c", 10 | username: "Billy", 11 | email: "balldash@live.com", 12 | password: "thunderbolt", 13 | }) 14 | }; 15 | const notUser = { 16 | email: "jadeclaw@notuser.com", 17 | }; 18 | 19 | const newPassword = { 20 | newPassword: "firebolt", 21 | 22 | }; 23 | const signed = signToken(user, user.password); 24 | export { 25 | user, notUser, newPassword, signed 26 | }; 27 | -------------------------------------------------------------------------------- /src/tests/controllers/resetPasswordTest/resetPasswordWithSinonTest.js: -------------------------------------------------------------------------------- 1 | import sinon from "sinon"; 2 | import chai from "chai"; 3 | import sinonChai from "sinon-chai"; 4 | import resetPasswordController from "../../../controllers/resetPasswordController"; 5 | import { 6 | user, notUser, newPassword, signed 7 | } from "./reset-test-data"; 8 | import db from "../../../models"; 9 | import sendgrid from "../../../utilities/sendgrid"; 10 | 11 | chai.use(sinonChai); 12 | 13 | const { expect } = chai; 14 | const sandbox = sinon.createSandbox(); 15 | 16 | afterEach(() => { sandbox.restore(); }); 17 | 18 | describe("send recover email", () => { 19 | it("should send a recovery email to user email in db", async () => { 20 | const res = { 21 | json: sinon.spy(), 22 | status: sandbox.stub().returns({ send: sinon.spy(), json: sinon.spy() }), 23 | 24 | }; 25 | const req = { 26 | json: sinon.spy(), 27 | status: sandbox.stub().returns({ send: sinon.spy() }), 28 | body: { email: user.email }, 29 | 30 | }; 31 | sandbox.stub(db.Users, "findOne").returns(user); 32 | sandbox.stub(sendgrid, "sendResetPasswordEmail").withArgs(user.email, user.id, signed, res).returns({ status: 200, message: "A reset email has been sent" }); 33 | resetPasswordController.recover(req, res); 34 | 35 | expect(db.Users.findOne).to.have.been.calledOnce.and.calledWith({ 36 | where: { email: user.email } 37 | }); 38 | }); 39 | }); 40 | describe("fail send recovery email", () => { 41 | it("should fail to send a recovery email to user not in db", async () => { 42 | const res = { 43 | json: sinon.spy(), 44 | status: sandbox.stub().returns({ send: sinon.spy(), json: sinon.spy() }), 45 | body: { email: notUser.email }, 46 | }; 47 | const req = { 48 | json: sinon.spy(), 49 | status: sandbox.stub().returns({ send: sinon.spy() }), 50 | body: { email: notUser.email }, 51 | }; 52 | sandbox.stub(db.Users, "findOne").returns(notUser); 53 | resetPasswordController.recover(req, res); 54 | expect(db.Users.findOne).to.have.been.calledOnce.and.calledWith({ 55 | where: { email: notUser.email } 56 | }); 57 | }); 58 | }); 59 | describe("change password", () => { 60 | it("should change password successfully", async () => { 61 | const res = { 62 | json: sinon.spy(), 63 | status: sandbox.stub().returns({ send: sinon.spy(), json: sinon.spy() }), 64 | body: { newPassword: newPassword.newPassword, id: user.id }, 65 | }; 66 | const req = { 67 | json: sinon.spy(), 68 | status: sandbox.stub().returns({ send: sinon.spy() }), 69 | body: { newPassword: newPassword.newPassword, id: user.id }, 70 | params: { id: user.id, token: signed } 71 | }; 72 | const { id } = user; 73 | sandbox.stub(db.Users, "findOne").resolves(user); 74 | resetPasswordController.reset(req, res); 75 | expect(db.Users.findOne).to.have.been.calledOnce.and.calledWith({ 76 | where: { id }, 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/tests/controllers/states/state-data.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | name: "Ondo", 3 | capital: "Akure", 4 | gallery: "https://en.wikipedia.org/wiki/Lusaka#/media/File:Downtown_Lusaka.JPG", 5 | }; 6 | const state2 = { 7 | capital: "Ikeja", 8 | gallery: "https://en.wikipedia.org/wiki/Lusaka#/media/File:Downtown_Lusaka.JPG", 9 | }; 10 | 11 | const state3 = { 12 | name: "Anambra", 13 | capital: "Awka", 14 | gallery: "https://en.wikipedia.org/wiki/Lusaka#/media/File:Downtown_Lusaka.JPG", 15 | }; 16 | 17 | const state4 = { 18 | id: "c72134f2-69e2-4a13-bb1c-e0d60d43afa3", 19 | countryId: "2e11e4a9-441b-4426-9521-39adc64ccfad", 20 | name: "Delta", 21 | capital: "Asaba", 22 | gallery: "https://en.wikipedia.org/wiki/Lusaka#/media/File:Downtown_Lusaka.JPG", 23 | createdAt: new Date(), 24 | updatedAt: new Date(), 25 | }; 26 | 27 | const state5 = { 28 | id: "c72134f2-69e2-4a13-bb1c-e0d60d43afa2", 29 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 30 | name: "Oyo", 31 | capital: "Ibadan", 32 | gallery: "https://en.wikipedia.org/wiki/Lusaka#/media/File:Downtown_Lusaka.JPG", 33 | createdAt: new Date(), 34 | updatedAt: new Date(), 35 | }; 36 | 37 | export { 38 | state, state2, state3, state4, state5 39 | }; 40 | -------------------------------------------------------------------------------- /src/tests/controllers/touristCenter/touristCenter-data.js: -------------------------------------------------------------------------------- 1 | const touristCenter = { 2 | name: "Victoria Falls", 3 | location: "Livingstone", 4 | gallery: "https://cdn.pixabay.com/photo/2017/04/13/09/11/waterfall-2227010_960_720.jpg", 5 | about: "Victoria Falls is a waterfall on the Zambezi River in southern Africa, which provides habitat for several unique species of plants and animals. It is located on the border between Zambia and Zimbabwe[1] and is considered to be one of the world's largest waterfalls due to its width of 1,708 metres" 6 | }; 7 | const touristCenter2 = { 8 | name: "Bhagdad Falls", 9 | gallery: "https://cdn.pixabay.com/photo/2017/04/13/09/11/waterfall-2227010_960_720.jpg", 10 | about: "Victoria Falls is a waterfall on the Zambezi River in southern Africa, which provides habitat for several unique species of plants and animals. It is located on the border between Zambia and Zimbabwe[1] and is considered to be one of the world's largest waterfalls due to its width of 1,708 metres" 11 | }; 12 | 13 | const touristCenter3 = { 14 | name: "Obudu Cattle Ranch", 15 | location: "Livingstone", 16 | gallery: "https://cdn.pixabay.com/photo/2017/04/13/09/11/waterfall-2227010_960_720.jpg", 17 | about: "Victoria Falls is a waterfall on the Zambezi River in southern Africa, which provides habitat for several unique species of plants and animals. It is located on the border between Zambia and Zimbabwe[1] and is considered to be one of the world's largest waterfalls due to its width of 1,708 metres" 18 | }; 19 | 20 | const touristCenter4 = { 21 | id: "8d585465-cd80-4030-b665-bdc3bbd3e575", 22 | countryId: "2e11e4a9-441b-4426-9521-39adc64ccfad", 23 | name: "Obudu cattle ranch", 24 | location: "Livingstone", 25 | gallery: "https://cdn.pixabay.com/photo/2017/04/13/09/11/waterfall-2227010_960_720.jpg", 26 | about: "Victoria Falls is a waterfall on the Zambezi River in southern Africa, which provides habitat for several unique species of plants and animals. It is located on the border between Zambia and Zimbabwe[1] and is considered to be one of the world's largest waterfalls due to its width of 1,708 metres", 27 | createdAt: new Date(), 28 | updatedAt: new Date(), 29 | }; 30 | 31 | const touristCenter5 = { 32 | id: "8d585465-cd80-4030-b665-bdc3bbd3e400", 33 | countryId: "6003fb36-5112-463e-a1f9-c8944e72412f", 34 | name: "Maclears beacon", 35 | location: "Cape Town", 36 | gallery: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/MBea.jpg/1200px-MBea.jpg", 37 | about: "Table Mountain is a flat-topped mountain forming a prominent landmark overlooking the city of Cape Town in South Africa. It is a significant tourist attraction, with many visitors using the cableway or hiking to the top.", 38 | createdAt: new Date(), 39 | updatedAt: new Date(), 40 | }; 41 | 42 | export { 43 | touristCenter, touristCenter2, touristCenter3, touristCenter4, touristCenter5 44 | }; 45 | -------------------------------------------------------------------------------- /src/tests/controllers/userProfileTest/profile-data.js: -------------------------------------------------------------------------------- 1 | const profile = { 2 | firstName: "Mary", 3 | lastName: "Kelvin", 4 | profilePicture: "http:/facebook.com" 5 | }; 6 | export { profile }; 7 | -------------------------------------------------------------------------------- /src/tests/controllers/userProfileTest/profile-test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import server from "../../../app"; 4 | import { user5 } from "../users/user-sign-in-test-data"; 5 | import { profile } from "./profile-data"; 6 | 7 | chai.should(); 8 | 9 | const { expect } = chai; 10 | chai.use(chaiHttp); 11 | 12 | describe("Update user profile", () => { 13 | let userToken; 14 | before(done => { 15 | chai 16 | .request(server) 17 | .post("/api/v1/users/signin") 18 | .set("Accept", "application/json") 19 | .send(user5) 20 | .end((err, res) => { 21 | if (err) throw err; 22 | userToken = res.body.token; 23 | done(); 24 | }); 25 | }); 26 | it("should update a user profile successfully", done => { 27 | chai 28 | .request(server) 29 | .patch("/api/v1/user-profile") 30 | .set("Authorization", `Bearer ${userToken}`) 31 | .set("Accept", "application/json") 32 | .send(profile) 33 | .end((err, res) => { 34 | expect(res).to.have.status(200); 35 | expect(res.body.message).to.equal("User profile updated"); 36 | done(); 37 | }); 38 | }); 39 | it("should only Update profile names with a strings", done => { 40 | chai 41 | .request(server) 42 | .patch("/api/v1/user-profile") 43 | .set("Authorization", `Bearer ${userToken}`) 44 | .set("Accept", "application/json") 45 | .send({ firstName: 86767, lastName: 787878 }) 46 | .end((err, res) => { 47 | expect(res).to.have.status(400); 48 | expect(res.body.error).to.equal("firstName must be a string. lastName must be a string"); 49 | done(); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/tests/controllers/userSignUpTest/user-test-data.js: -------------------------------------------------------------------------------- 1 | const user = { 2 | username: "GarryT", 3 | email: "Garrasdy@gmail.com", 4 | password: "123456", 5 | }; 6 | 7 | const user2 = { 8 | email: "Garry@gmail.com", 9 | password: "123456", 10 | }; 11 | 12 | const user3 = { 13 | username: "Grace", 14 | email: "Garrasdy@gmail.com", 15 | password: "123456", 16 | }; 17 | 18 | const user4 = { 19 | username: "GarryT", 20 | email: "Grace@gmail.com", 21 | password: "123456", 22 | }; 23 | export { 24 | user, 25 | user2, 26 | user3, 27 | user4 28 | }; 29 | -------------------------------------------------------------------------------- /src/tests/controllers/userSignUpTest/user-test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import server from "../../../app"; 4 | import { 5 | user, 6 | user2, 7 | user3, 8 | user4 9 | } from "./user-test-data"; 10 | 11 | chai.should(); 12 | 13 | chai.use(chaiHttp); 14 | 15 | describe("Should test all users", async () => { 16 | describe("/api/v1/users/signup should create a user", () => { 17 | it("it should create a user with complete details successfully", done => { 18 | chai 19 | .request(server) 20 | .post("/api/v1/users/signup") 21 | .set("Accept", "application/json") 22 | .send(user) 23 | .end((err, res) => { 24 | res.should.have.status(201); 25 | res.body.should.be.a("object"); 26 | res.body.should.have.property("status").eql(201); 27 | res.body.should.have.property("message").eql("User created!"); 28 | res.body.should.have.property("data"); 29 | done(); 30 | }); 31 | }); 32 | it("it should not create a user with incomplete details", done => { 33 | chai 34 | .request(server) 35 | .post("/api/v1/users/signup") 36 | .set("Accept", "application/json") 37 | .send(user2) 38 | .end((err, res) => { 39 | res.should.have.status(400); 40 | done(); 41 | }); 42 | }); 43 | it("it should not signup a user with an already registered email", done => { 44 | chai 45 | .request(server) 46 | .post("/api/v1/users/signup") 47 | .set("Accept", "application/json") 48 | .send(user3) 49 | .end((err, res) => { 50 | res.should.have.status(409); 51 | res.body.should.have.property("error").eql("Email already used by another user."); 52 | done(); 53 | }); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/tests/controllers/users/user-sign-in-test-data.js: -------------------------------------------------------------------------------- 1 | const user = { 2 | email: "garrasdy@gmail.com", 3 | password: "123456", 4 | }; 5 | const user2 = { 6 | email: "garry@gmail.com", 7 | }; 8 | const user3 = { 9 | email: "obioflagos@gmail.com", 10 | password: "fghygvh", 11 | }; 12 | const user4 = { 13 | email: "ufuoma@gmail.com", 14 | password: "12345", 15 | }; 16 | const user5 = { 17 | email: "godspower@gmail.com", 18 | password: "12345", 19 | }; 20 | 21 | const user6 = { 22 | email: "francis@gmail.com", 23 | password: "12345" 24 | }; 25 | 26 | const user7 = { 27 | email: "fiyin@gmail.com", 28 | password: "12345", 29 | }; 30 | 31 | export { 32 | user, user2, user3, user4, user5, user6, user7 33 | }; 34 | -------------------------------------------------------------------------------- /src/tests/controllers/users/user-sign-in-test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import server from "../../../app"; 4 | import { 5 | user, 6 | user2, 7 | user3, 8 | user7 9 | } from "./user-sign-in-test-data"; 10 | 11 | chai.should(); 12 | chai.use(chaiHttp); 13 | describe("Should test all users", async () => { 14 | describe("/api/v1/users/signin should sign in a user", () => { 15 | it("it should sign in a user with complete details successfully", done => { 16 | chai 17 | .request(server) 18 | .post("/api/v1/users/signin") 19 | .set("Accept", "application/json") 20 | .send(user) 21 | .end((err, res) => { 22 | res.should.have.status(200); 23 | res.body.should.be.a("object"); 24 | res.body.should.have.property("message").eql("User Logged in!"); 25 | done(); 26 | }); 27 | }); 28 | it("it should not sign in a user with incomplete details", done => { 29 | chai 30 | .request(server) 31 | .post("/api/v1/users/signin") 32 | .set("Accept", "application/json") 33 | .send(user2) 34 | .end((err, res) => { 35 | res.should.have.status(400); 36 | done(); 37 | }); 38 | }); 39 | it("it should not sign in a user that is de-activated", done => { 40 | chai 41 | .request(server) 42 | .post("/api/v1/users/signin") 43 | .set("Accept", "application/json") 44 | .send(user7) 45 | .end((err, res) => { 46 | res.should.have.status(403); 47 | done(); 48 | }); 49 | }); 50 | it("it should not sign in a user without a registered email", done => { 51 | chai 52 | .request(server) 53 | .post("/api/v1/users/signin") 54 | .set("Accept", "application/json") 55 | .send(user3) 56 | .end((err, res) => { 57 | res.should.have.status(404); 58 | res.body.should.have.property("error").eql("Email does not exist."); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/tests/controllers/users/user-test-data.js: -------------------------------------------------------------------------------- 1 | const user = { 2 | username: "garryT", 3 | email: "garrasdy@gmail.com", 4 | password: "123456", 5 | }; 6 | const user2 = { 7 | email: "garry@gmail.com", 8 | password: "123456", 9 | }; 10 | const user3 = { 11 | username: "grace", 12 | email: "garrasdy@gmail.com", 13 | password: "123456", 14 | }; 15 | 16 | export { 17 | user, 18 | user2, 19 | user3 20 | }; 21 | -------------------------------------------------------------------------------- /src/tests/controllers/users/user-test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import server from "../../../app"; 4 | import { 5 | user, 6 | user2, 7 | user3 8 | } from "./user-test-data"; 9 | import sendGrid from "../../../utilities/sendgrid"; 10 | 11 | sendGrid.sandboxMode(); 12 | chai.should(); 13 | chai.use(chaiHttp); 14 | describe("Should test all users", async () => { 15 | describe("/api/v1/users/signup should create a user", () => { 16 | it("it should create a user with complete details successfully", done => { 17 | chai 18 | .request(server) 19 | .post("/api/v1/users/signup") 20 | .set("Accept", "application/json") 21 | .send(user) 22 | .end((err, res) => { 23 | res.should.have.status(201); 24 | res.body.should.be.a("object"); 25 | res.body.should.have.property("status").eql(201); 26 | res.body.should.have.property("message").eql("User created! An email has been sent to you to verify your account"); 27 | done(); 28 | }); 29 | }); 30 | it("it should verify a user's account", done => { 31 | chai 32 | .request(server) 33 | .get(`/api/v1/users/signup/verify/${user.email}`) 34 | .end((err, res) => { 35 | res.should.have.status(200); 36 | res.body.should.have.property("message").eql("User Verified successfully!"); 37 | res.body.data.should.have.property("verified").eql(true); 38 | done(); 39 | }); 40 | }); 41 | it("it should not create a user with incomplete details", done => { 42 | chai 43 | .request(server) 44 | .post("/api/v1/users/signup") 45 | .set("Accept", "application/json") 46 | .send(user2) 47 | .end((err, res) => { 48 | res.should.have.status(400); 49 | done(); 50 | }); 51 | }); 52 | it("it should not signup a user with an already registered email", done => { 53 | chai 54 | .request(server) 55 | .post("/api/v1/users/signup") 56 | .set("Accept", "application/json") 57 | .send(user3) 58 | .end((err, res) => { 59 | res.should.have.status(409); 60 | done(); 61 | }); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/tests/google-oauth/google-mock-data.js: -------------------------------------------------------------------------------- 1 | const newUser = { 2 | id: "98e0350f-ed09-46b0-83d7-8a135afeaf89", 3 | username: "xavier", 4 | email: "Xavierfrancis@gmail.com", 5 | password: "", 6 | role: "User" 7 | }; 8 | 9 | const user = { 10 | id: "98e0350f-ed09-46b0-83d7-8a135afeaf89", 11 | username: "xavier", 12 | email: "Xavierfrancis@gmail.com", 13 | password: "", 14 | role: "User" 15 | }; 16 | 17 | export { newUser, user }; 18 | -------------------------------------------------------------------------------- /src/tests/google-oauth/google-test.spec.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import server from "../../app"; 4 | import { newUser, user } from "./google-mock-data"; 5 | 6 | chai.should(); 7 | 8 | chai.use(chaiHttp); 9 | 10 | describe("Signin a user with google oauth", () => { 11 | it("it should signin a user with google oauth", done => { 12 | chai.request(server).get("/auth/google"); 13 | done(); 14 | }); 15 | it("it should redirect a user to the callback URL", done => { 16 | chai.request(server).get("/auth/google/callback"); 17 | done(); 18 | }); 19 | it("it should save new user", done => { 20 | chai.request(server).post("/api/v1/users/signup").send(newUser); 21 | done(); 22 | }); 23 | it("it should not signup a user with an already registered id", done => { 24 | chai 25 | .request(server) 26 | .post("/api/v1/users/signup") 27 | .send(user); 28 | done(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/tests/index-test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import chaiHttp from "chai-http"; 3 | import server from "../app"; 4 | 5 | chai.should(); 6 | 7 | chai.use(chaiHttp); 8 | 9 | describe("/ should display Welcome to Know Africa", () => { 10 | it("it should get the welcome page", done => { 11 | chai 12 | .request(server) 13 | .get("/") 14 | .end((err, res) => { 15 | res.should.have.status(200); 16 | done(); 17 | }); 18 | }); 19 | }); 20 | 21 | export default describe; 22 | -------------------------------------------------------------------------------- /src/tests/index.js: -------------------------------------------------------------------------------- 1 | import "./index-test"; 2 | import "./models/user.spec"; 3 | import "./models/profile.spec"; 4 | import "./models/food.spec"; 5 | import "./models/music.spec"; 6 | import "./models/comment.spec"; 7 | import "./models/newsletter.spec"; 8 | import "./models/historicalfact.test"; 9 | import "./models/State.spec"; 10 | import "./models/TouristCenter.spec"; 11 | import "./models/country.spec"; 12 | import "./models/ethnicgroup.spec"; 13 | import "./controllers/users/user-test"; 14 | import "./controllers/users/user-sign-in-test"; 15 | import "./google-oauth/google-test.spec"; 16 | import "./controllers/userProfileTest/profile-test"; 17 | import "./controllers/admin/addCountry.test"; 18 | import "./controllers/admin/activateUser.test"; 19 | import "./controllers/admin/deActivateUser.test"; 20 | import "./controllers/music/music.test"; 21 | import "./controllers/touristCenter/touristCenter.test"; 22 | import "./controllers/ethnicGroup/ethnicGroup.test"; 23 | import "./controllers/food/food.test"; 24 | import "./controllers/newsletter/newsletter.test"; 25 | import "./intergration/CountriesRoutes.spec"; 26 | import "./unit/controllers/CountriesController.spec"; 27 | import "./controllers/states/state.test"; 28 | import "./controllers/historicalFacts/historicalFacts.test"; 29 | import "./controllers/resetPasswordTest/resetPasswordWithSinonTest"; 30 | import "./intergration/CommentsRoutes.spec"; 31 | -------------------------------------------------------------------------------- /src/tests/models/State.spec.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinonChai from "sinon-chai"; 3 | 4 | chai.use(sinonChai); 5 | 6 | const { expect } = chai; 7 | 8 | const { 9 | sequelize, 10 | dataTypes, 11 | checkModelName, 12 | checkPropertyExists, 13 | } = require("sequelize-test-helpers"); 14 | 15 | const stateModel = require("../../models/state"); 16 | const CountryModel = require("../../models/countries"); 17 | 18 | describe("src/models/State", () => { 19 | const State = stateModel(sequelize, dataTypes); 20 | const state = new State(); 21 | checkModelName(State)("States"); 22 | 23 | context("properties", () => { 24 | ["capital", "gallery", "name"].forEach(checkPropertyExists(state)); 25 | }); 26 | 27 | context("associations", () => { 28 | const Countries = "Zambia"; 29 | 30 | before(() => { 31 | State.associate({ Countries }); 32 | State.belongsTo(CountryModel, { as: "states", foreignKey: "countryId" }); 33 | }); 34 | 35 | it("defined a belongsTo association with Countries", () => { 36 | expect(State.belongsTo).to.have.been.calledWith(Countries); 37 | expect(State.belongsTo).to.have.been.calledWith(CountryModel, { 38 | as: "states", 39 | foreignKey: "countryId", 40 | }); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/tests/models/TouristCenter.spec.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinonChai from "sinon-chai"; 3 | 4 | chai.use(sinonChai); 5 | 6 | const { expect } = chai; 7 | 8 | const { 9 | sequelize, 10 | dataTypes, 11 | checkModelName, 12 | checkPropertyExists, 13 | } = require("sequelize-test-helpers"); 14 | 15 | const touristCenterModel = require("../../models/touristCenter"); 16 | const CountryModel = require("../../models/countries"); 17 | 18 | describe("src/models/Tourist Center", () => { 19 | const TouristCenter = touristCenterModel(sequelize, dataTypes); 20 | const touristCenter = new TouristCenter(); 21 | checkModelName(TouristCenter)("TouristCenters"); 22 | 23 | context("properties", () => { 24 | ["location", "gallery", "name"].forEach(checkPropertyExists(touristCenter)); 25 | }); 26 | 27 | context("associations", () => { 28 | const Countries = "Zambia"; 29 | 30 | before(() => { 31 | TouristCenter.associate({ Countries }); 32 | TouristCenter.belongsTo(CountryModel, { 33 | as: "touristCenters", 34 | foreignKey: "countryId", 35 | }); 36 | }); 37 | 38 | it("defined a belongsTo association with Country", () => { 39 | expect(TouristCenter.belongsTo).to.have.been.calledWith(CountryModel, { 40 | as: "touristCenters", 41 | foreignKey: "countryId", 42 | }); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/tests/models/comment.spec.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinonChai from "sinon-chai"; 3 | 4 | chai.use(sinonChai); 5 | 6 | const { expect } = chai; 7 | 8 | const { 9 | sequelize, 10 | dataTypes, 11 | checkModelName, 12 | checkPropertyExists, 13 | } = require("sequelize-test-helpers"); 14 | 15 | const CommentModel = require("../../models/comment.js"); 16 | 17 | describe("src/models/comment", () => { 18 | const Comment = CommentModel(sequelize, dataTypes); 19 | const comment = new Comment(); 20 | 21 | checkModelName(Comment)("Comments"); 22 | 23 | context("properties", () => { 24 | ["userId", "comment"].forEach(checkPropertyExists(comment)); 25 | }); 26 | 27 | context("associations", () => { 28 | const Users = "John Doe"; 29 | 30 | before(() => { 31 | Comment.associate({ Users }); 32 | }); 33 | 34 | it("defined a belongsTo association with Users", () => { 35 | expect(Comment.belongsTo).to.have.been.calledWith(Users); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/tests/models/country.js: -------------------------------------------------------------------------------- 1 | import { 2 | sequelize, 3 | dataTypes, 4 | checkModelName, 5 | checkPropertyExists, 6 | } from "sequelize-test-helpers"; 7 | import chai, { expect } from "chai"; 8 | 9 | import sinonChai from "sinon-chai"; 10 | import CountryModel from "../../models/countries"; 11 | 12 | chai.use(sinonChai); 13 | 14 | describe("src/models/countries", () => { 15 | const Country = CountryModel(sequelize, dataTypes); 16 | const country = new Country(); 17 | 18 | checkModelName(Country)("Countries"); 19 | 20 | context("properties", () => { 21 | [ 22 | "nameOfCountry", 23 | "gallery", 24 | "capital", 25 | "population", 26 | "officialLanguage", 27 | "region", 28 | "currency", 29 | ].forEach(checkPropertyExists(country)); 30 | }); 31 | 32 | context("associations", () => { 33 | const EthnicGroups = "EthnicGroup data"; 34 | 35 | before(() => { 36 | Country.associate({ EthnicGroups }); 37 | }); 38 | 39 | it("defined a hasMany association with EthnicGroup", () => { 40 | expect(Country.hasMany).to.have.been.calledWith(EthnicGroups); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/tests/models/country.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | sequelize, 3 | dataTypes, 4 | checkModelName, 5 | checkPropertyExists, 6 | } from "sequelize-test-helpers"; 7 | import chai, { expect } from "chai"; 8 | 9 | import sinonChai from "sinon-chai"; 10 | import CountryModel from "../../models/countries"; 11 | 12 | chai.use(sinonChai); 13 | 14 | describe("src/models/countries", () => { 15 | const Country = CountryModel(sequelize, dataTypes); 16 | const country = new Country(); 17 | 18 | checkModelName(Country)("Countries"); 19 | 20 | context("properties", () => { 21 | [ 22 | "nameOfCountry", 23 | "gallery", 24 | "capital", 25 | "population", 26 | "officialLanguage", 27 | "region", 28 | "currency", 29 | ].forEach(checkPropertyExists(country)); 30 | }); 31 | 32 | context("associations", () => { 33 | const EthnicGroups = "EthnicGroup data"; 34 | 35 | before(() => { 36 | Country.associate({ EthnicGroups }); 37 | }); 38 | 39 | it("defined a hasMany association with EthnicGroup", () => { 40 | expect(Country.hasMany).to.have.been.calledWith(EthnicGroups); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/tests/models/ethnicgroup.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | sequelize, 3 | dataTypes, 4 | checkModelName, 5 | checkPropertyExists, 6 | } from "sequelize-test-helpers"; 7 | import chai, { expect } from "chai"; 8 | 9 | import sinonChai from "sinon-chai"; 10 | import EthnicGroupModel from "../../models/ethnicgroup"; 11 | 12 | chai.use(sinonChai); 13 | 14 | describe("src/models/ethnicgroup", () => { 15 | const EthnicGroup = EthnicGroupModel(sequelize, dataTypes); 16 | const ethnicGroup = new EthnicGroup(); 17 | 18 | checkModelName(EthnicGroup)("EthnicGroups"); 19 | 20 | context("properties", () => { 21 | ["name", "festivals", "dressing", "language", "gallery", "culturalPractices"].forEach( 22 | checkPropertyExists(ethnicGroup), 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/tests/models/food.spec.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import sinonChai from "sinon-chai"; 3 | import { 4 | sequelize, 5 | dataTypes, 6 | checkModelName, 7 | checkPropertyExists, 8 | } from "sequelize-test-helpers"; 9 | 10 | import foodModel from "../../models/food"; 11 | 12 | chai.use(sinonChai); 13 | 14 | describe("src/models/food", () => { 15 | const Food = foodModel(sequelize, dataTypes); 16 | const food = new Food(); 17 | 18 | checkModelName(Food)("Foods"); 19 | 20 | context("properties", () => { 21 | ["countryId", "type", "methodOfPreparation", "gallery"].forEach( 22 | checkPropertyExists(food) 23 | ); 24 | }); 25 | context("associations", () => { 26 | const Countries = "Countries"; 27 | 28 | before(() => { 29 | Food.associate({ Countries }); 30 | }); 31 | 32 | it("defined a belongsTo association with Countries", () => { 33 | expect(Food.belongsTo).to.have.been.calledWith(Countries); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/tests/models/historicalfact.test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinonChai from "sinon-chai"; 3 | 4 | import { 5 | sequelize, 6 | dataTypes, 7 | checkModelName, 8 | checkUniqueIndex, 9 | checkPropertyExists, 10 | makeMockModels, 11 | } from "sequelize-test-helpers"; 12 | 13 | import HistoricalfactModel from "../../models/historicalfact"; 14 | 15 | chai.use(sinonChai); 16 | 17 | const { expect } = chai; 18 | 19 | describe("src/models/historicalfact", () => { 20 | const Historicalfact = HistoricalfactModel(sequelize, dataTypes); 21 | const historicalfact = new Historicalfact(); 22 | 23 | checkModelName(Historicalfact)("Historicalfacts"); 24 | 25 | context("properties", () => { 26 | ["countryId", "about", "location", "gallery"].forEach( 27 | checkPropertyExists(historicalfact) 28 | ); 29 | }); 30 | 31 | context("associations", () => { 32 | const Countries = "Nigeria"; 33 | 34 | before(() => { 35 | Historicalfact.associate({ Countries }); 36 | }); 37 | 38 | it("defined a belongsTo association with Countries", () => { 39 | expect(Historicalfact.belongsTo).to.have.been.calledWith(Countries); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/tests/models/music.spec.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | import sinonChai from "sinon-chai"; 3 | import { 4 | sequelize, 5 | dataTypes, 6 | checkModelName, 7 | checkPropertyExists, 8 | } from "sequelize-test-helpers"; 9 | 10 | import musicModel from "../../models/music"; 11 | 12 | describe("src/models/music", () => { 13 | const Music = musicModel(sequelize, dataTypes); 14 | const music = new Music(); 15 | checkModelName(Music)("Music"); 16 | 17 | context("properties", () => { 18 | ["countryId", "category", "information", "gallery"].forEach(checkPropertyExists(music)); 19 | }); 20 | context("associations", () => { 21 | const Countries = "Nigeria"; 22 | 23 | before(() => { 24 | Music.associate({ Countries }); 25 | }); 26 | 27 | it("defined a belongsTo association with Countries", () => { 28 | expect(Music.belongsTo).to.have.been.calledWith(Countries); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/tests/models/newsletter.spec.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinonChai from "sinon-chai"; 3 | 4 | chai.use(sinonChai); 5 | 6 | const { expect } = chai; 7 | 8 | const { 9 | sequelize, 10 | dataTypes, 11 | checkModelName, 12 | checkPropertyExists, 13 | } = require("sequelize-test-helpers"); 14 | 15 | const NewsletterModel = require("../../models/newsletter.js"); 16 | 17 | describe("src/models/newsletter", () => { 18 | const Newsletter = NewsletterModel(sequelize, dataTypes); 19 | const newsletter = new Newsletter(); 20 | 21 | checkModelName(Newsletter)("Newsletters"); 22 | 23 | context("properties", () => { 24 | ["title", "message"].forEach(checkPropertyExists(newsletter)); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/tests/models/profile.spec.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | 3 | import sinonChai from "sinon-chai"; 4 | 5 | import { 6 | sequelize, 7 | dataTypes, 8 | checkModelName, 9 | checkPropertyExists, 10 | } from "sequelize-test-helpers"; 11 | 12 | import ProfileModel from "../../models/profile"; 13 | 14 | chai.use(sinonChai); 15 | describe("src/models/Profile", () => { 16 | const Profile = ProfileModel(sequelize, dataTypes); 17 | const profile = new Profile(); 18 | checkModelName(Profile)("Profiles"); 19 | context("properties", () => { 20 | ["firstName", "lastName", "profilePicture", "userId"].forEach( 21 | checkPropertyExists(profile) 22 | ); 23 | }); 24 | context("associations", () => { 25 | const Users = "Profile data"; 26 | before(() => { 27 | Profile.associate({ Users }); 28 | }); 29 | it("defined a belongsTo association with Profile", () => { 30 | expect(Profile.belongsTo).to.have.been.calledWith(Users); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/tests/models/user.spec.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from "chai"; 2 | 3 | import sinonChai from "sinon-chai"; 4 | import { 5 | sequelize, 6 | dataTypes, 7 | checkModelName, 8 | checkPropertyExists, 9 | } from "sequelize-test-helpers"; 10 | 11 | import UserModel from "../../models/User"; 12 | 13 | chai.use(sinonChai); 14 | describe("src/models/User", () => { 15 | const User = UserModel(sequelize, dataTypes); 16 | const user = new User(); 17 | checkModelName(User)("Users"); 18 | context("properties", () => { 19 | ["email", "username", "password", "role", "verified"].forEach( 20 | checkPropertyExists(user), 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/tests/unit/controllers/CountriesController.spec.js: -------------------------------------------------------------------------------- 1 | import sinon from "sinon"; 2 | import chai from "chai"; 3 | import sinonChai from "sinon-chai"; 4 | import countriesController from "../../../controllers/country/country"; 5 | import countriesMockData from "../../__mock__/countriesMockData"; 6 | import countryMockData from "../../__mock__/countryMockData"; 7 | import db from "../../../models"; 8 | 9 | chai.use(sinonChai); 10 | 11 | const { expect } = chai; 12 | 13 | describe("countries controllers", () => { 14 | it("should call the countries model findAll method with expected arguments", async () => { 15 | const res = { 16 | json: sinon.spy(), 17 | status: sinon.stub().returns({ send: sinon.spy() }), 18 | }; 19 | const req = { 20 | }; 21 | sinon.stub(db.Countries, "findAll").returns(countriesMockData); 22 | await countriesController.listCountries(req, res); 23 | expect(db.Countries.findAll).to.have.been.calledOnce.and.calledWith({ 24 | attributes: ["id", "nameOfCountry", "gallery", "capital", "population", "officialLanguage", "region", "currency"], 25 | include: [{ model: db.TouristCenters, as: "touristCenters" }, { model: db.States, as: "states" }, { model: db.EthnicGroups, as: "ethnicGroups" }, { model: db.Music, as: "music" }, { model: db.Foods, as: "Food" }, { model: db.Historicalfacts, as: "historicalFacts" }, { model: db.EthnicGroups, as: "ethnicGroups" }, { model: db.Music, as: "music" }, { model: db.Foods, as: "Food" }, { model: db.Comments, as: "comments" }] 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/utilities/Jwt.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import dotenv from "dotenv"; 3 | 4 | dotenv.config(); 5 | const secretKey = process.env.JWT_KEY; 6 | /** 7 | * 8 | */ 9 | export default class jwtHelper { 10 | /** 11 | * @param {object} payload - The details to be signed 12 | * @param {string} secret - The JWT secret key 13 | * @returns {string} The JWT signed token 14 | */ 15 | static async generateToken(payload, secret = secretKey) { 16 | const token = await jwt.sign(payload, secret, { expiresIn: "1d" }); 17 | return token; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utilities/hashPassword.js: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcrypt"; 2 | 3 | export default password => { 4 | const salt = bcrypt.genSaltSync(10); 5 | const pass = bcrypt.hashSync(password, salt); 6 | return pass; 7 | }; 8 | -------------------------------------------------------------------------------- /src/utilities/index.js: -------------------------------------------------------------------------------- 1 | import "./util"; 2 | -------------------------------------------------------------------------------- /src/utilities/signToken.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | export default (payload, key) => { 4 | const token = jwt.sign(payload, key, { expiresIn: 3600 }); 5 | return token; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utilities/util.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable valid-jsdoc */ 2 | /** 3 | * 4 | */ 5 | export default class Util { 6 | /** 7 | * 8 | */ 9 | constructor() { 10 | this.statusCode = null; 11 | this.type = null; 12 | this.data = null; 13 | this.token = null; 14 | this.message = null; 15 | this.error = null; 16 | } 17 | 18 | /** 19 | * @param {number} statusCode - The res status code 20 | * @param {string} message - The success message 21 | * @param {string} token - The JWT signed token 22 | * @param {object} data - The res body object 23 | */ 24 | setSuccess(statusCode, message, token, data) { 25 | this.statusCode = statusCode; 26 | this.message = message; 27 | this.token = token; 28 | this.data = data; 29 | this.type = "success"; 30 | } 31 | 32 | /** 33 | * @param {number} statusCode - The res status code 34 | * @param {string} message - The error message 35 | * @param {string} error - The error message 36 | */ 37 | setError(statusCode, message, error) { 38 | this.statusCode = statusCode; 39 | this.message = message; 40 | this.error = error; 41 | this.type = "error"; 42 | } 43 | 44 | /** 45 | * @param {object} res - The res body object 46 | * @returns {object} Success or Error message 47 | */ 48 | send(res) { 49 | const result = { 50 | status: this.statusCode, 51 | message: this.message, 52 | data: this.data, 53 | token: this.token, 54 | }; 55 | if (this.type === "success") { 56 | return res.status(this.statusCode).json(result); 57 | } 58 | return res.status(this.statusCode).json({ 59 | status: this.statusCode, 60 | error: this.error 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/validation/commentValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const validateComment = comment => { 4 | const schema = Joi.object({ 5 | userId: Joi.string().required() 6 | .empty().guid({ version: "uuidv4" }) 7 | .messages({ 8 | "any.required": "userID is required.", 9 | "string.empty": "userID cannot be an empty field.", 10 | "string.base": "userID must be a string.", 11 | "string.guid": "userID must be a UUID" 12 | }), 13 | relatedId: Joi.string().required() 14 | .empty().guid({ version: "uuidv4" }) 15 | .messages({ 16 | "any.required": "related id is required.", 17 | "string.empty": "related id cannot be an empty field.", 18 | "string.base": "related id must be a string.", 19 | "string.guid": "related id must be a UUID" 20 | }), 21 | comment: Joi.string().required().min(3).max(500) 22 | .empty() 23 | .messages({ 24 | "any.required": "comment is required.", 25 | "string.empty": "comment cannot be an empty field.", 26 | "string.base": "comment must be a string.", 27 | "string.min": "comment length must be at least 3 characters long", 28 | "string.max": "only 500 characters allowed for a comment", 29 | }), 30 | }).options({ abortEarly: true }); 31 | return schema.validate(comment); 32 | }; 33 | export default validateComment; 34 | -------------------------------------------------------------------------------- /src/validation/countryValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const validation = country => { 4 | const schema = Joi.object({ 5 | nameOfCountry: Joi.string().required().valid("Nigeria", 6 | "Ethiopia", 7 | "Egypt", 8 | "Democratic Republic of the Congo", 9 | "Tanzania", 10 | "South Africa", 11 | "Kenya", 12 | "Uganda", 13 | "Algeria", 14 | "Sudan", 15 | "Morocco", 16 | "Mozambique", 17 | "Ghana", 18 | "Angola", 19 | "Somalia", 20 | "Ivory Coast", 21 | "Madagascar", 22 | "Cameroon", 23 | "Burkina Faso", 24 | "Niger", 25 | "Malawi", 26 | "Zambia", 27 | "Mali", 28 | "Senegal", 29 | "Zimbabwe", 30 | "Chad", 31 | "Tunisia", 32 | "Guinea", 33 | "Rwanda", 34 | "Benin", 35 | "Burundi", 36 | "South Sudan", 37 | "Eritrea", 38 | "Sierra Leone", 39 | "Togo", 40 | "Libya", 41 | "Central African Republic", 42 | "Mauritania", 43 | "Republic of the Congo", 44 | "Liberia", 45 | "Namibia", 46 | "Botswana", 47 | "Lesotho", 48 | "Gambia", 49 | "Gabon", 50 | "Guinea-Bissau", 51 | "Mauritius", 52 | "Equatorial Guinea", 53 | "Eswatini", 54 | "Djibouti", 55 | "Réunion (France)", 56 | "Comoros", 57 | "Cape Verde", 58 | "Mayotte (France)", 59 | "São Tomé and Príncipe", 60 | "Seychelles") 61 | .messages({ 62 | "any.required": "Sorry, country name is required.", 63 | "any.only": "Country must be an African country.", 64 | "string.empty": "Country cannot be an empty field.", 65 | "string.base": "Country name must contain only alphabetical characters." 66 | }), 67 | gallery: Joi.string().required() 68 | .empty() 69 | .messages({ 70 | "any.required": "An image is required.", 71 | "string.empty": "gallery cannot be an empty field.", 72 | "string.base": "Please provide a valid link." 73 | 74 | }), 75 | capital: Joi.string().required() 76 | .empty() 77 | .messages({ 78 | "any.required": "Name of capital is required.", 79 | "string.empty": "Capital cannot be an empty field.", 80 | "string.base": "Capital must contain only alphabetical characters." 81 | }), 82 | population: Joi.number().integer().required() 83 | .empty() 84 | .messages({ 85 | "any.required": "Population size is required.", 86 | "number.empty": "Population cannot be an empty field." 87 | }), 88 | officialLanguage: Joi.string().required() 89 | .empty() 90 | .messages({ 91 | "any.required": "An official language is required.", 92 | "string.empty": "Official language cannot be an empty field.", 93 | "string.base": "Language must contain only alphabetical characters." 94 | }), 95 | region: Joi.string().required() 96 | .empty() 97 | .messages({ 98 | "any.required": "Sorry, region is required.", 99 | "string.empty": "Region cannot be an empty field.", 100 | "string.base": "Region must contain only alphabetical characters." 101 | }), 102 | currency: Joi.string().required() 103 | .empty() 104 | .messages({ 105 | "any.required": "Sorry, currency is required.", 106 | "string.empty": "Currency cannot be an empty field.", 107 | "string.base": "Currency must contain only alphabetical characters." 108 | }), 109 | }).messages({ 110 | "object.unknown": "You have used an invalid key." 111 | }).options({ abortEarly: false }); 112 | return schema.validate(country); 113 | }; 114 | 115 | export { validation }; 116 | -------------------------------------------------------------------------------- /src/validation/ethnicgroup.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const validateId = ids => { 4 | const schema = Joi.object({ 5 | id: Joi.string().required() 6 | .empty().guid({ version: "uuidv4" }) 7 | .messages({ 8 | "any.required": "ID not provided. Please provide an ID.", 9 | "string.empty": "ID cannot be an empty field.", 10 | "string.base": "ID must be a string.", 11 | "string.guid": "ID must be a UUID" 12 | }), 13 | }).messages({ 14 | "object.unknown": "You have used an invalid key." 15 | }).options({ abortEarly: false }); 16 | return schema.validate(ids); 17 | }; 18 | 19 | const validation = user => { 20 | const schema = Joi.object({ 21 | countryId: Joi.string().required().empty().guid({ version: "uuidv4" }) 22 | .messages({ 23 | "any.required": "ID not provided. Please provide an ID.", 24 | "string.empty": "ID cannot be an empty field.", 25 | "string.base": "ID must be a string.", 26 | "string.guid": "ID must be a UUID" 27 | }), 28 | name: Joi.string().required().empty() 29 | .messages({ 30 | "any.required": "Name of ethnic group is required", 31 | "string.empty": "Name of ethnic group cannot be an empty field", 32 | }), 33 | festivals: Joi.string().required().empty() 34 | .messages({ 35 | "any.required": "Dressing style is required", 36 | "string.empty": "Festival cannot be an empty field", 37 | }), 38 | dressing: Joi.string().required().empty() 39 | .messages({ 40 | "any.required": "Dressing style is required", 41 | "string.empty": "Dressing style cannot be an empty field", 42 | }), 43 | language: Joi.string().required().empty() 44 | .messages({ 45 | "any.required": "Language is required", 46 | "string.empty": "Language cannot be an empty field", 47 | }), 48 | gallery: Joi.string().required().empty() 49 | .messages({ 50 | "any.required": "An image is required.", 51 | "string.empty": "Gallery cannot be an empty field.", 52 | "string.base": "Please provide a valid link." 53 | }), 54 | culturalPractices: Joi.string().required().empty() 55 | .messages({ 56 | "any.required": "CulturalPractices is required.", 57 | "string.empty": "CulturalPractices cannot be an empty field.", 58 | }), 59 | }).options({ abortEarly: false }); 60 | return schema.validate(user); 61 | }; 62 | 63 | export { validation, validateId, }; 64 | -------------------------------------------------------------------------------- /src/validation/foodValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const foodValidation = food => { 4 | const schema = Joi.object({ 5 | countryId: Joi.string().required() 6 | .empty().guid({ version: "uuidv4" }) 7 | .messages({ 8 | "any.required": "countryId is required.", 9 | "string.empty": "countryId cannot be an empty field.", 10 | "string.base": "countryId must be a string.", 11 | "string.guid": "countryId must be a UUID" 12 | }), 13 | foodName: Joi.string().required().empty().messages({ 14 | "any.required": "Please provide a food name", 15 | "string.empty": "Food name cannot be an empty field.", 16 | "string.base": "Food name must contain only alphabetical characters." 17 | }), 18 | methodOfPreparation: Joi.string().required().empty() 19 | .messages({ 20 | "string.empty": "Method of Preparation cannot be an empty field.", 21 | "any.required": "Method of Preparation is required.", 22 | }), 23 | gallery: Joi.string() 24 | .messages({ 25 | "string.base": "Please provide a valid link." 26 | }) 27 | }).messages({ 28 | "object.unknown": "You have used an invalid key." 29 | }); 30 | return schema.validate(food); 31 | }; 32 | 33 | export { 34 | foodValidation 35 | }; 36 | -------------------------------------------------------------------------------- /src/validation/historicalFactsValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const validation = historicalFacts => { 4 | const schema = Joi.object({ 5 | countryId: Joi.string().required() 6 | .empty().guid({ version: "uuidv4" }) 7 | .messages({ 8 | "any.required": "countryId is required.", 9 | "string.empty": "countryId cannot be an empty field.", 10 | "string.base": "countryId must be a string.", 11 | "string.guid": "countryId must be a UUID" 12 | }), 13 | about: Joi.string().required() 14 | .empty() 15 | .messages({ 16 | "any.required": "about is required.", 17 | "string.empty": "about cannot be an empty field.", 18 | "string.base": "about must be a string." 19 | }), 20 | location: Joi.string().required() 21 | .empty() 22 | .messages({ 23 | "any.required": "location is required.", 24 | "string.empty": "location cannot be an empty field.", 25 | "string.base": "location must be a string." 26 | }), 27 | gallery: Joi.string().required() 28 | .empty() 29 | .messages({ 30 | "any.required": "An image is required.", 31 | "string.empty": "gallery cannot be an empty field.", 32 | "string.base": "Please provide a valid link." 33 | }), 34 | }).messages({ 35 | "object.unknown": "You have used an invalid key." 36 | }).options({ abortEarly: false }); 37 | return schema.validate(historicalFacts); 38 | }; 39 | 40 | const validateId = ids => { 41 | const schema = Joi.object({ 42 | id: Joi.string().required() 43 | .empty().guid({ version: "uuidv4" }) 44 | .messages({ 45 | "any.required": "ID not provided. Please provide an ID.", 46 | "string.empty": "ID cannot be an empty field.", 47 | "string.base": "ID must be a string.", 48 | "string.guid": "ID must be a UUID" 49 | }), 50 | countryId: Joi.string().guid({ version: "uuidv4" }) 51 | .messages({ 52 | "string.guid": "CountryId must be a UUID" 53 | }) 54 | }).messages({ 55 | "object.unknown": "You have used an invalid key." 56 | }).options({ abortEarly: false }); 57 | return schema.validate(ids); 58 | }; 59 | 60 | const validateLocation = location => { 61 | const schema = Joi.object({ 62 | location: Joi.string().required() 63 | .empty() 64 | .messages({ 65 | "any.required": "Location not provided. Please provide a Location.", 66 | "string.empty": "Location cannot be an empty field.", 67 | "string.base": "Location must be a string.", 68 | }) 69 | }).messages({ 70 | "object.unknown": "You have used an invalid key." 71 | }).options({ abortEarly: false }); 72 | return schema.validate(location); 73 | }; 74 | 75 | export { validation, validateId, validateLocation }; 76 | -------------------------------------------------------------------------------- /src/validation/index.js: -------------------------------------------------------------------------------- 1 | import "./userValidation"; 2 | import "./countryValidation"; 3 | -------------------------------------------------------------------------------- /src/validation/musicValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const validation = music => { 4 | const schema = Joi.object({ 5 | countryId: Joi.string().required() 6 | .empty().guid({ version: "uuidv4" }) 7 | .messages({ 8 | "any.required": "countryId is required.", 9 | "string.empty": "countryId cannot be an empty field.", 10 | "string.base": "countryId must be a string.", 11 | "string.guid": "countryId must be a UUID" 12 | }), 13 | category: Joi.string().required() 14 | .empty() 15 | .messages({ 16 | "any.required": "category is required.", 17 | "string.empty": "category cannot be an empty field.", 18 | "string.base": "category must be a string." 19 | }), 20 | information: Joi.string().required() 21 | .empty() 22 | .messages({ 23 | "any.required": "information is required.", 24 | "string.empty": "information cannot be an empty field.", 25 | "string.base": "information must be a string." 26 | }), 27 | gallery: Joi.string().required() 28 | .empty() 29 | .messages({ 30 | "any.required": "An image is required.", 31 | "string.empty": "gallery cannot be an empty field.", 32 | "string.base": "Please provide a valid link." 33 | }), 34 | }).messages({ 35 | "object.unknown": "You have used an invalid key." 36 | }).options({ abortEarly: false }); 37 | return schema.validate(music); 38 | }; 39 | 40 | const validateId = ids => { 41 | const schema = Joi.object({ 42 | id: Joi.string().required() 43 | .empty().guid({ version: "uuidv4" }) 44 | .messages({ 45 | "any.required": "ID not provided. Please provide an ID.", 46 | "string.empty": "ID cannot be an empty field.", 47 | "string.base": "ID must be a string.", 48 | "string.guid": "ID must be a UUID" 49 | }), 50 | countryId: Joi.string().guid({ version: "uuidv4" }) 51 | .messages({ 52 | "string.guid": "CountryId must be a UUID" 53 | }) 54 | }).messages({ 55 | "object.unknown": "You have used an invalid key." 56 | }).options({ abortEarly: false }); 57 | return schema.validate(ids); 58 | }; 59 | 60 | export { validation, validateId }; 61 | -------------------------------------------------------------------------------- /src/validation/newsletterValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const newsletterValidation = user => { 4 | const schema = Joi.object({ 5 | title: Joi.string().required().min(3).max(255) 6 | .empty() 7 | .messages({ 8 | "any.required": "Please add a title to this newsletter", 9 | "string.empty": "Sorry, newsletter title is required!", 10 | }), 11 | message: Joi.string().required().min(20).max(10000) 12 | .empty() 13 | .messages({ 14 | "any.required": "Please add a message to your newsletter", 15 | "string.empty": "To send a newsletter, you have to add a message", 16 | }), 17 | }).messages({ 18 | "object.unknown": "You have used an invalid key." 19 | }); 20 | return schema.validate(user); 21 | }; 22 | 23 | export default newsletterValidation; 24 | -------------------------------------------------------------------------------- /src/validation/stateValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const validation = state => { 4 | const schema = Joi.object({ 5 | name: Joi.string().required() 6 | .messages({ 7 | "any.required": "Sorry, state name is required.", 8 | "any.only": "State must be an African state.", 9 | "string.empty": "State cannot be an empty field.", 10 | "string.base": "State name must contain only alphabetical characters." 11 | }), 12 | countryId: Joi.string().required() 13 | .empty().guid({ version: "uuidv4" }) 14 | .messages({ 15 | "any.required": "countryId is required.", 16 | "string.empty": "countryId cannot be an empty field.", 17 | "string.base": "countryId must be a string.", 18 | "string.guid": "countryId must be a UUID" 19 | }), 20 | gallery: Joi.string().required() 21 | .empty() 22 | .messages({ 23 | "any.required": "An image is required.", 24 | "string.empty": "Image field cannot be an empty field.", 25 | "string.base": "Please provide a valid link." 26 | 27 | }), 28 | capital: Joi.string().required() 29 | .empty() 30 | .messages({ 31 | "any.required": "Name of capital is required.", 32 | "string.empty": "Capital cannot be an empty field.", 33 | "string.base": "Capital must contain only alphabetical characters." 34 | }), 35 | }).messages({ 36 | "object.unknown": "You have used an invalid key." 37 | }).options({ abortEarly: false }); 38 | return schema.validate(state); 39 | }; 40 | 41 | const validateId = ids => { 42 | const schema = Joi.object({ 43 | id: Joi.string().required() 44 | .empty().guid({ version: "uuidv4" }) 45 | .messages({ 46 | "any.required": "ID not provided. Please provide an ID.", 47 | "string.empty": "ID cannot be an empty field.", 48 | "string.base": "ID must be a string.", 49 | "string.guid": "ID must be a UUID" 50 | }), 51 | countryId: Joi.string().guid({ version: "uuidv4" }) 52 | .messages({ 53 | "string.guid": "CountryId must be a UUID" 54 | }) 55 | }).messages({ 56 | "object.unknown": "You have used an invalid key." 57 | }).options({ abortEarly: false }); 58 | return schema.validate(ids); 59 | }; 60 | 61 | export { validation, validateId }; 62 | -------------------------------------------------------------------------------- /src/validation/touristCenterValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const validation = touristCenter => { 4 | const schema = Joi.object({ 5 | countryId: Joi.string().required() 6 | .empty().guid({ version: "uuidv4" }) 7 | .messages({ 8 | "any.required": "countryId is required.", 9 | "string.empty": "countryId cannot be an empty field.", 10 | "string.base": "countryId must be a string.", 11 | "string.guid": "countryId must be a UUID" 12 | }), 13 | name: Joi.string().required() 14 | .empty() 15 | .messages({ 16 | "any.required": "name is required.", 17 | "string.empty": "name cannot be an empty field.", 18 | "string.base": "name must be a string." 19 | }), 20 | gallery: Joi.string().required() 21 | .empty() 22 | .messages({ 23 | "any.required": "An image is required.", 24 | "string.empty": "gallery cannot be an empty field.", 25 | "string.base": "Please provide a valid link." 26 | }), 27 | location: Joi.string().required() 28 | .empty() 29 | .messages({ 30 | "any.required": "location is required.", 31 | "string.empty": "location cannot be an empty field.", 32 | "string.base": "location must contain only alphabetical characters." 33 | }), 34 | about: Joi.string().required() 35 | .empty() 36 | .messages({ 37 | "any.required": "about is required.", 38 | "string.empty": "about cannot be an empty field.", 39 | "string.base": "about must contain only alphabetical characters." 40 | }), 41 | }).messages({ 42 | "object.unknown": "You have used an invalid key." 43 | }).options({ abortEarly: false }); 44 | return schema.validate(touristCenter); 45 | }; 46 | 47 | const validateId = ids => { 48 | const schema = Joi.object({ 49 | id: Joi.string().required() 50 | .empty().guid({ version: "uuidv4" }) 51 | .messages({ 52 | "any.required": "ID not provided. Please provide an ID.", 53 | "string.empty": "ID cannot be an empty field.", 54 | "string.base": "ID must be a string.", 55 | "string.guid": "ID must be a UUID" 56 | }), 57 | countryId: Joi.string().guid({ version: "uuidv4" }) 58 | .messages({ 59 | "string.guid": "CountryId must be a UUID" 60 | }) 61 | }).messages({ 62 | "object.unknown": "You have used an invalid key." 63 | }).options({ abortEarly: false }); 64 | return schema.validate(ids); 65 | }; 66 | 67 | export { validation, validateId }; 68 | -------------------------------------------------------------------------------- /src/validation/userValidation.js: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const registerValidation = user => { 4 | const schema = Joi.object({ 5 | username: Joi.string().required().alphanum().min(3) 6 | .max(255) 7 | .empty() 8 | .messages({ 9 | "any.required": "Sorry, username is required", 10 | "string.alphanum": "Sorry, Username must contain only alphanumeric characters", 11 | "string.empty": "username cannot be an empty field", 12 | "string.min": "username should have a minimum length of 3 and a maximum length of 255" 13 | }), 14 | email: Joi.string().required().email({ minDomainSegments: 2, tlds: { allow: ["com", "net", "uk", "co"] } }).min(5) 15 | .max(100) 16 | .empty() 17 | .messages({ 18 | "any.required": "Sorry, email is required", 19 | "string.empty": "Sorry, Email cannot be an empty field", 20 | "string.email": "Please enter a valid email", 21 | }), 22 | password: Joi.string().required().empty().min(5) 23 | .max(1024) 24 | .pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")) 25 | .messages({ 26 | "any.required": "Sorry, password is required", 27 | "string.pattern.base": "password must contain only alphanumeric characters.", 28 | "string.empty": "Sorry, password cannot be an empty field", 29 | "string.min": "password should have a minimum length of 5" 30 | }), 31 | }).messages({ 32 | "object.unknown": "You have used an invalid key." 33 | }).options({ abortEarly: false }); 34 | return schema.validate(user); 35 | }; 36 | const loginValidation = user => { 37 | const schema = Joi.object({ 38 | email: Joi.string().required().email({ minDomainSegments: 2, tlds: { allow: ["com", "net", "uk", "co", "io"] } }).min(5) 39 | .max(100) 40 | .empty() 41 | .messages({ 42 | "any.required": "Sorry, email is required", 43 | "string.email": "Please enter a valid email", 44 | "string.empty": "Sorry, Email cannot be an empty field", 45 | }), 46 | password: Joi.string().required().min(5).max(1024) 47 | .pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")) 48 | .messages({ 49 | "string.pattern.base": "Password must contain only alphanumeric characters.", 50 | "string.empty": "Sorry, password cannot be an empty field", 51 | "string.min": "Password should have a minimum length of 5" 52 | }), 53 | }).messages({ 54 | "object.unknown": "You have used an invalid key." 55 | }); 56 | return schema.validate(user); 57 | }; 58 | 59 | const profileValidate = profile => { 60 | const schema = Joi.object({ 61 | firstName: Joi.string().max(40).empty() 62 | .messages({ 63 | "string.base": "firstName must be a string", 64 | "string.max": "firstName cannot be above 40 characters", 65 | "string.empty": "Sorry, firstName cannot be an empty field" 66 | }), 67 | lastName: Joi.string().max(40).empty() 68 | .messages({ 69 | "string.base": "lastName must be a string", 70 | "string.max": "lastName cannot be above 40 characters", 71 | "string.empty": "Sorry, lastName cannot be an empty field", 72 | }), 73 | profilePicture: Joi.string().empty() 74 | .messages({ 75 | "string.base": "Please provide a valid link", 76 | "string.empty": "Sorry, profilePicture cannot be an empty field" 77 | }), 78 | }).messages({ 79 | "object.unknown": "You have used an invalid key." 80 | }).options({ abortEarly: false }); 81 | return schema.validate(profile); 82 | }; 83 | 84 | const subscriberValidation = user => { 85 | const schema = Joi.object({ 86 | firstName: Joi.string().required().min(3).max(255) 87 | .empty() 88 | .messages({ 89 | "any.required": "Sorry, first name is required", 90 | "string.empty": "Sorry, You have to enter your first name", 91 | }), 92 | email: Joi.string().required().email({ minDomainSegments: 2, tlds: { allow: ["com", "net", "uk", "co"] } }).min(5) 93 | .max(100) 94 | .empty() 95 | .messages({ 96 | "any.required": "Sorry, email is required", 97 | "string.empty": "Sorry, Email cannot be an empty field", 98 | "string.email": "Please enter a valid email", 99 | }), 100 | }).messages({ 101 | "object.unknown": "You have used an invalid key." 102 | }); 103 | return schema.validate(user); 104 | }; 105 | 106 | export { 107 | registerValidation, 108 | loginValidation, 109 | subscriberValidation, 110 | profileValidate 111 | }; 112 | --------------------------------------------------------------------------------