├── .babelrc ├── .gitignore ├── client ├── js │ ├── app.js │ ├── components │ │ ├── Page.jsx │ │ ├── Header.jsx │ │ ├── DevelopersList.js │ │ └── Developers.jsx │ └── tests │ │ └── App.test.js ├── index.html ├── dist │ └── style.css └── styles │ └── main.css ├── server ├── routes │ └── index.js ├── models │ └── developers.js ├── controllers │ └── DeveloperController.js └── index.js ├── app.json ├── .editorconfig ├── docs └── tutorials.md ├── .circleci └── config.yml ├── webpack.config.js ├── tests └── api.js ├── package.json └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["react", "es2015", "stage-2"] } 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | .vscode 4 | .DS_Store 5 | package-lock.json -------------------------------------------------------------------------------- /client/js/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Page from './components/Page' 4 | import '../styles/main.css' 5 | 6 | ReactDOM.render(, document.getElementById('app')); 7 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const DeveloperController = require('../controllers/DeveloperController'); 2 | 3 | const routes = (app) => { 4 | app.route('/developers') 5 | .get(DeveloperController.getDevelopers) 6 | .post(DeveloperController.newDeveloper); 7 | } 8 | module.exports = routes; 9 | -------------------------------------------------------------------------------- /client/js/components/Page.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from './Header'; 3 | import Developers from './Developers'; 4 | 5 | const Page = () => { 6 | return ( 7 |
8 |
9 | 10 |
11 | ) 12 | }; 13 | 14 | export default Page; 15 | -------------------------------------------------------------------------------- /client/js/tests/App.test.js: -------------------------------------------------------------------------------- 1 | // const React = require('react'); 2 | // const ReactDOM = require('react-dom'); 3 | // const Page = require('../components/Page'); 4 | 5 | // it('renders without crashing', () => { 6 | // const div = document.createElement('div'); 7 | // ReactDOM.render(Page, div); 8 | // }); 9 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phdevsdir", 3 | "scripts": { 4 | }, 5 | "env": { 6 | "MONGODB_URI": { 7 | "required": true 8 | } 9 | }, 10 | "formation": { 11 | }, 12 | "addons": [ 13 | "mongolab" 14 | ], 15 | "buildpacks": [ 16 | { 17 | "url": "heroku/nodejs" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /client/js/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Header = () => { 4 | return ( 5 |
6 |
7 | PHDEVSDIR 8 |
9 |
10 | 11 |
12 |
13 | ) 14 | } 15 | 16 | export default Header; 17 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PH Devs Directory 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | # EditorConfig is awesome: http://EditorConfig.org 4 | 5 | # top-most EditorConfig file 6 | root = true 7 | 8 | # Unix-style newlines with a newline ending every file 9 | [*] 10 | end_of_line = lf 11 | insert_final_newline = true 12 | 13 | # Matches multiple files with brace expansion notation 14 | # Set default charset 15 | [*.{js,html,css}] 16 | charset = utf-8 17 | 18 | # Tab indentation (no size specified) 19 | [Makefile] 20 | indent_style = tab 21 | 22 | # Indentation override for all JS under lib directory 23 | [**.js] 24 | indent_style = space 25 | indent_size = 2 26 | 27 | # Matches the exact files either package.json or .travis.yml 28 | [{package.json,.travis.yml}] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | -------------------------------------------------------------------------------- /docs/tutorials.md: -------------------------------------------------------------------------------- 1 | # Here's an example of posting data to the api using json 2 | 3 | - Paste the app link in the url bar, set the request method from "GET" to "POST" 4 | - Now go under HEADERS(just after "Authorization") and make sure content-Type is set application/json 5 | - then go under BODY(just after Headers) to select ur dataType: select either "x-www-form-encoded" or "raw" [this example uses raw] 6 | - After you have selected raw to your left appears a category of dataTypes, default is text, change it to "JSON(application/json)". 7 | - enter data to be submited like so: (check code below) 8 | ``` 9 | { 10 | "first_name": "Jane", 11 | "last_name": "Joe", 12 | "email": "jane@email.com", 13 | "stack": "JS, PHP", 14 | "github_url": "github.com/janeDoe" 15 | } 16 | 17 | ``` 18 | - Click the SEND button (behind the url bar) and it should return a response with "status : 200" if successful. -------------------------------------------------------------------------------- /server/models/developers.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var developersSchema = new Schema({ 5 | first_name: { 6 | type: String, 7 | required: true, 8 | trim: true 9 | }, 10 | last_name: { 11 | type: String, 12 | required: true, 13 | trim: true 14 | }, 15 | email: { 16 | type: String, 17 | required: true, 18 | trim: true, 19 | unique: true, 20 | lowercase: true 21 | }, 22 | stack: { 23 | type: Array, 24 | required: false 25 | }, 26 | github_url: { 27 | type: String, 28 | required: true, 29 | unique: true, 30 | lowercase: true 31 | }, 32 | created_at: { 33 | type: Date, 34 | required: false 35 | }, 36 | updated_at: { 37 | type: Number, 38 | required: false 39 | } 40 | }); 41 | 42 | var Developers = mongoose.model('Developers', developersSchema); 43 | 44 | module.exports = Developers; -------------------------------------------------------------------------------- /server/controllers/DeveloperController.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Developers = require('../models/developers'); 3 | 4 | exports.getDevelopers = (req, res, next) => { 5 | return Developers 6 | .find({}) 7 | .exec((err, users) => { 8 | if (err) { 9 | return res.status(500).send({ data: { error: err, status: 500, success: false } }); 10 | } else { 11 | return res.status(200).send({ data: { users, status: 200, success: true } }); 12 | } 13 | }); 14 | } 15 | 16 | exports.newDeveloper = (req, res, next) => { 17 | const developerData = { 18 | first_name: req.body.first_name, 19 | last_name: req.body.last_name, 20 | email: req.body.email, 21 | stack: req.body.stack.split(',').map((elem) => elem.trim()), 22 | github_url: req.body.github_url 23 | }; 24 | developers = new Developers(developerData).save() 25 | .then(() => { 26 | return res.status(201).send({ data: { user: developerData, status: 201, success: true } }); 27 | }).catch((err) => { 28 | console.log(err); 29 | return res.status(500).send({ data: { error: err, status: 500, success: false } }); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:7.10 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: yarn install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: yarn test 38 | machine: 39 | services: 40 | - mongodb 41 | -------------------------------------------------------------------------------- /client/js/components/DevelopersList.js: -------------------------------------------------------------------------------- 1 | const devList = [ 2 | { 3 | "id": 1, 4 | "image": "https://avatars3.githubusercontent.com/u/9336187?v=4&s=460", 5 | "name": "Francis Sunday", 6 | "giturl": "https://github.com/codehakase", 7 | "stack": ["Go Lang", "PHP", "React"] 8 | }, 9 | { 10 | "id": 2, 11 | "image": "https://avatars2.githubusercontent.com/u/24508232?v=4&s=460", 12 | "name": "Joseph Uchenna", 13 | "giturl": "https://github.com/afrikhero", 14 | "stack": ["JavaScript", "PHP", "MySQL"] 15 | }, 16 | { 17 | "id": 3, 18 | "image": "https://avatars1.githubusercontent.com/u/25697914?v=4&s=460", 19 | "name": "Ndifreke Friday", 20 | "giturl": "https://github.com/NddyAndy", 21 | "stack": ["JavaScript"] 22 | }, 23 | { 24 | "id": 4, 25 | "image": "https://avatars3.githubusercontent.com/u/15184445?v=4&s=460", 26 | "name": "Michael Ozoemena", 27 | "giturl": "https://github.com/THEozmic", 28 | "stack": ["Python", "PHP", "React"] 29 | } 30 | ] 31 | 32 | export default devList; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const debug = process.env.NODE_ENV !== 'production'; 2 | const webpack = require('webpack'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | module.exports = { 6 | module: { 7 | loaders: [ 8 | { 9 | test: /\.(js|jsx)?$/, 10 | loader: 'babel-loader', 11 | query: { 12 | presets: ['react', 'es2015', 'stage-0'], 13 | plugins: ['react-html-attrs', 'transform-decorators-legacy', 14 | 'transform-class-properties'], 15 | } 16 | }, 17 | { 18 | test: /\.(scss|css)?$/, 19 | use: ExtractTextPlugin.extract({ 20 | fallback: 'style-loader', 21 | // resolve-url-loader may be chained before sass-loader if necessary 22 | use: ['css-loader',] 23 | }) 24 | }, 25 | { test: /\.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, 26 | loader: 'file-loader?name=fonts/[name].[ext]' 27 | } 28 | ] 29 | }, 30 | output: { 31 | path: `${__dirname}/client/dist/`, 32 | filename: 'bundle.min.js', 33 | publicPath: '/dist/' 34 | }, 35 | resolve: { 36 | modules: ['node_modules', 'client/js'], 37 | extensions: ['.js', '.jsx', '.json', '.css', '.scss'] 38 | }, 39 | plugins: [ 40 | new webpack.ProvidePlugin({ 41 | $: 'jquery', 42 | jQuery: 'jquery', 43 | 'window.jQuery': 'jquery', 44 | Hammer: 'hammerjs/hammer' 45 | }), 46 | new ExtractTextPlugin('style.css'), 47 | new webpack.optimize.OccurrenceOrderPlugin(), 48 | new webpack.optimize.UglifyJsPlugin({ mangle: false, sourcemap: true }) 49 | ], 50 | }; 51 | -------------------------------------------------------------------------------- /client/js/components/Developers.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | 4 | 5 | class Developers extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | devList: [] 10 | } 11 | } 12 | 13 | componentWillMount() { 14 | axios.get('/api/v1/developers').then( 15 | (result) => { 16 | this.setState({ 17 | devList: result.data.data.users 18 | }) 19 | 20 | let newDevList = []; 21 | console.log(this.state.devList, '==>>>'); 22 | this.state.devList.map((dev) => { 23 | axios.get(`https://api.github.com/users/${dev.github_url.split('/')[3]}`) 24 | .then((result) => { 25 | const user = result.data; 26 | dev.image = user.avatar_url; 27 | newDevList.push(dev); 28 | this.setState({ devList: newDevList }); 29 | }); 30 | }); 31 | } 32 | ) 33 | 34 | } 35 | 36 | render() { 37 | const listItems = this.state.devList.map((d) => 38 |
39 |
40 | 41 |
42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 | {d.first_name + " " + d.last_name} 50 | 51 | 52 | { 53 | d.stack.map((e, i) => 54 | {e} 55 | ) 56 | } 57 | 58 | View on Github 59 |
60 |
61 | ); 62 | return ( 63 |
64 | {listItems} 65 |
66 | ); 67 | } 68 | } 69 | 70 | export default Developers; 71 | -------------------------------------------------------------------------------- /tests/api.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = "test"; 2 | 3 | let mongoose = require("mongoose"); 4 | let Developer = require("../server/models/developers"); 5 | 6 | // Require dev-dependencies 7 | let chai = require("chai"); 8 | let chaiHttp = require("chai-http"); 9 | let server = require("../server/index"); 10 | let should = chai.should(); 11 | let faker = require('faker'); 12 | 13 | chai.use(chaiHttp); 14 | 15 | describe("Developers", () => { 16 | beforeEach( (done) => { 17 | Developer.findOne( {last_name: "Doe"}, null, {limit: 1}).remove(); 18 | done(); 19 | }); 20 | describe("/Get developers", () => { 21 | it("it should GET all developers when the route is hit", done => { 22 | chai 23 | .request(server) 24 | .get("/api/v1/developers") 25 | .end((err, res) => { 26 | res.should.have.status(200); 27 | res.should.be.an("object"); 28 | done(); 29 | }); 30 | }); 31 | }); 32 | describe("/POST developers", () => { 33 | it("it should create a new dev record when all parameters are provided", done => { 34 | let developer = { 35 | first_name: faker.name.findName(), 36 | last_name: faker.name.findName(), 37 | email: faker.internet.email(), 38 | stack: "Python, JavaScript, PHP, Go", 39 | github_url: faker.internet.url() 40 | } 41 | chai.request(server) 42 | .post('/api/v1/developers') 43 | .send(developer) 44 | .end( (err, res) => { 45 | res.should.have.status(201 || 500) 46 | res.body.should.be.an('object') 47 | res.body.should.have.property('data'); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | }); 53 | 54 | describe("Server", () => { 55 | describe("/Get server status", () => { 56 | it("it should return Server is Up! when route /isAlive is hit", done => { 57 | chai 58 | .request(server) 59 | .get("/isAlive") 60 | .end((err, res) => { 61 | res.should.have.status(200); 62 | done(); 63 | }); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phdevsdir", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha ./tests --timeout 10000 --exit && mocha ./client/js/tests --timeout 10000 --exit", 8 | "build-client": "./node_modules/.bin/webpack ./client/js/app -w", 9 | "build-server": "babel -d ./build ./server -s -w", 10 | "start": "node ./build", 11 | "start-dev": "nodemon ./build" 12 | }, 13 | "repository": { 14 | "type": "git" 15 | }, 16 | "dependencies": { 17 | "async": "^2.5.0", 18 | "axios": "^0.17.0", 19 | "babel-polyfill": "^6.26.0", 20 | "bcrypt": "^1.0.2", 21 | "body-parser": "^1.17.2", 22 | "chai": "^4.1.2", 23 | "concurrently": "^3.5.0", 24 | "connect-multiparty": "^2.0.0", 25 | "cookie-parser": "^1.4.3", 26 | "dotenv": "^4.0.0", 27 | "express": "^4.15.3", 28 | "express-session": "^1.15.3", 29 | "faker": "^4.1.0", 30 | "helmet": "^3.6.1", 31 | "lodash": "^4.17.4", 32 | "mime": "^1.3.6", 33 | "mocha": "^4.0.1", 34 | "moment": "^2.18.1", 35 | "mongodb": "^2.2.30", 36 | "mongoose": "^4.11.3", 37 | "morgan": "^1.8.2", 38 | "path": "^0.12.7", 39 | "react": "~15.6.1", 40 | "react-dom": "~15.6.1", 41 | "react-router-dom": "^4.2.2", 42 | "shortid": "^2.2.8", 43 | "socket.io": "^2.0.3", 44 | "validator": "^8.0.0" 45 | }, 46 | "engines": { 47 | "node": "8.1.2", 48 | "npm": "5.0.3" 49 | }, 50 | "keywords": [], 51 | "author": "", 52 | "license": "ISC", 53 | "devDependencies": { 54 | "babel-cli": "~6.24.1", 55 | "babel-core": "~6.17.0", 56 | "babel-loader": "~6.2.0", 57 | "babel-plugin-add-module-exports": "^0.1.2", 58 | "babel-plugin-react-html-attrs": "^2.0.0", 59 | "babel-plugin-transform-class-properties": "^6.3.13", 60 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 61 | "babel-plugin-transform-es2015-destructuring": "^6.23.0", 62 | "babel-preset-es2015": "^6.24.1", 63 | "babel-preset-react": "^6.24.1", 64 | "babel-preset-stage-0": "^6.24.1", 65 | "chai-http": "^3.0.0", 66 | "css-loader": "^0.28.7", 67 | "extract-text-webpack-plugin": "^3.0.1", 68 | "file-loader": "^1.1.5", 69 | "nodemon": "^1.12.1", 70 | "sass-loader": "^6.0.6", 71 | "style-loader": "^0.19.0", 72 | "webpack": "^3.7.1" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /client/dist/style.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | font-family: 'Source Sans Pro', sans-serif; 3 | margin: 0px; 4 | background-color: #ececec; 5 | } 6 | .developers { 7 | margin: 30px 100px; 8 | text-align: center; 9 | display: inline-block; 10 | } 11 | 12 | .developer { 13 | display: inline-block; 14 | box-shadow: 0px 0px 8px 1px #d4d1d1; 15 | border-radius: 3px; 16 | background-color: #fff; 17 | margin: 10px 0.5em; 18 | width: 250px; 19 | text-align: left; 20 | float: left; 21 | } 22 | 23 | .developer > .profile-pic > img { 24 | width: 100%; 25 | border-top-left-radius: 3px; 26 | border-top-right-radius: 3px; 27 | } 28 | 29 | .profile-data { 30 | padding: 20px; 31 | color: #454545; 32 | display: inline-block; 33 | bottom: 35px; 34 | box-sizing: border-box; 35 | padding-top: 5px; 36 | } 37 | 38 | .profile-data > span { 39 | display: inline-block; 40 | width: 100%; 41 | margin: 5px 0px; 42 | } 43 | 44 | .developer-name { 45 | font-size: 25px; 46 | height: 35px; 47 | width: 210px !important; 48 | text-overflow: ellipsis; 49 | white-space: nowrap; 50 | overflow: hidden; 51 | } 52 | 53 | .tech { 54 | padding: 5px; 55 | background-color: #2196F3; 56 | color: #fff; 57 | border-radius: 3px; 58 | font-size: 12px; 59 | margin-right: 5px; 60 | } 61 | 62 | .profile-data a { 63 | font-size: 12px; 64 | text-decoration: none; 65 | padding: 5px; 66 | background-color: #607D8B; 67 | color: #fff; 68 | margin-top: 10px; 69 | display: inline-block; 70 | border-radius: 3px; 71 | } 72 | 73 | .v-badge { 74 | font-size: 20px; 75 | font-weight: bolder; 76 | background-color: #E91E63; 77 | padding: 5px; 78 | border-radius: 50%; 79 | cursor: pointer; 80 | margin: 0px 5px; 81 | width: 25px; 82 | position: absolute; 83 | display: inline-block; 84 | box-shadow: 0px 0px 7px 1px rgba(19, 19, 19, 0.43); 85 | color: #ffffff; 86 | text-align: center; 87 | } 88 | 89 | .exp { 90 | font-size: 12px; 91 | display: inline; 92 | width: unset !important; 93 | padding: 5px; 94 | border-radius: 3px; 95 | background-color: #FF9800; 96 | box-shadow: 0px 0px 3px 1px rgba(128, 128, 128, 0.33); 97 | color: #fff; 98 | } 99 | 100 | .header { 101 | height: 50px; 102 | background-color: #2195f3; 103 | box-shadow: 0px 3px 10px 0px #bdbcbc; 104 | padding: 0px 7px; 105 | } 106 | 107 | .logo { 108 | display: inline-block; 109 | font-size: 30px; 110 | color: #fff; 111 | margin: 5px; 112 | } 113 | 114 | .search { 115 | display: inline-block; 116 | float: right; 117 | margin: 5px; 118 | padding: 5px; 119 | margin-right: 0px; 120 | } 121 | 122 | .search > input { 123 | padding: 10px; 124 | border-radius: 3px; 125 | border: none; 126 | } 127 | 128 | .profile-pic { 129 | height: 250px; 130 | } 131 | 132 | .badge-container { 133 | position: relative; 134 | bottom: 15px; 135 | left: 204px; 136 | } -------------------------------------------------------------------------------- /client/styles/main.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | font-family: 'Source Sans Pro', sans-serif; 3 | margin: 0px; 4 | background-color: #ececec; 5 | } 6 | .developers { 7 | margin: 30px 100px; 8 | text-align: center; 9 | display: inline-block; 10 | } 11 | 12 | .developer { 13 | display: inline-block; 14 | box-shadow: 0px 0px 8px 1px #d4d1d1; 15 | border-radius: 3px; 16 | background-color: #fff; 17 | margin: 10px 0.5em; 18 | width: 250px; 19 | text-align: left; 20 | float: left; 21 | } 22 | 23 | .developer > .profile-pic > img { 24 | width: 100%; 25 | border-top-left-radius: 3px; 26 | border-top-right-radius: 3px; 27 | } 28 | 29 | .profile-data { 30 | padding: 20px; 31 | color: #454545; 32 | display: inline-block; 33 | bottom: 35px; 34 | box-sizing: border-box; 35 | padding-top: 5px; 36 | } 37 | 38 | .profile-data > span { 39 | display: inline-block; 40 | width: 100%; 41 | margin: 5px 0px; 42 | } 43 | 44 | .developer-name { 45 | font-size: 25px; 46 | height: 35px; 47 | width: 210px !important; 48 | text-overflow: ellipsis; 49 | white-space: nowrap; 50 | overflow: hidden; 51 | } 52 | 53 | .tech { 54 | padding: 5px; 55 | background-color: #2196F3; 56 | color: #fff; 57 | border-radius: 3px; 58 | font-size: 12px; 59 | margin-right: 5px; 60 | } 61 | 62 | .profile-data a { 63 | font-size: 12px; 64 | text-decoration: none; 65 | padding: 5px; 66 | background-color: #607D8B; 67 | color: #fff; 68 | margin-top: 10px; 69 | display: inline-block; 70 | border-radius: 3px; 71 | } 72 | 73 | .v-badge { 74 | font-size: 20px; 75 | font-weight: bolder; 76 | background-color: #E91E63; 77 | padding: 5px; 78 | border-radius: 50%; 79 | cursor: pointer; 80 | margin: 0px 5px; 81 | width: 25px; 82 | position: absolute; 83 | display: inline-block; 84 | box-shadow: 0px 0px 7px 1px rgba(19, 19, 19, 0.43); 85 | color: #ffffff; 86 | text-align: center; 87 | } 88 | 89 | .exp { 90 | font-size: 12px; 91 | display: inline; 92 | width: unset !important; 93 | padding: 5px; 94 | border-radius: 3px; 95 | background-color: #FF9800; 96 | box-shadow: 0px 0px 3px 1px rgba(128, 128, 128, 0.33); 97 | color: #fff; 98 | } 99 | 100 | .header { 101 | height: 50px; 102 | background-color: #2195f3; 103 | box-shadow: 0px 3px 10px 0px #bdbcbc; 104 | padding: 0px 7px; 105 | } 106 | 107 | .logo { 108 | display: inline-block; 109 | font-size: 30px; 110 | color: #fff; 111 | margin: 5px; 112 | } 113 | 114 | .search { 115 | display: inline-block; 116 | float: right; 117 | margin: 5px; 118 | padding: 5px; 119 | margin-right: 0px; 120 | } 121 | 122 | .search > input { 123 | padding: 10px; 124 | border-radius: 3px; 125 | border: none; 126 | } 127 | 128 | .profile-pic { 129 | height: 250px; 130 | } 131 | 132 | .badge-container { 133 | position: relative; 134 | bottom: 15px; 135 | left: 204px; 136 | } -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | require("babel-polyfill"); 2 | 3 | require("dotenv").config(); 4 | 5 | const express = require("express"), 6 | http = require("http"), 7 | socketio = require("socket.io"), 8 | router = express.Router(), 9 | helmet = require("helmet"), 10 | logger = require("morgan"), 11 | session = require("express-session"), 12 | cookieParser = require("cookie-parser"), 13 | bcrypt = require("bcrypt"), 14 | crypto = require("crypto"), 15 | path = require("path"), 16 | shortid = require("shortid"), 17 | fs = require("fs"), 18 | fileParser = require("connect-multiparty")(), 19 | validator = require("validator"), 20 | mime = require("mime"), 21 | bodyParser = require("body-parser"), 22 | mongoose = require("mongoose"), 23 | _ = require("lodash"), 24 | app = express(); 25 | 26 | const environment = process.env.NODE_ENV || "development"; 27 | mongoose.Promise = global.Promise; 28 | 29 | const online_DB_uri = process.env.MONGODB_URI, 30 | local_DB_uri = (process.env.NODE_ENV === "test") ? "mongodb://codehakasee:codehakase1@ds121015.mlab.com:21015/phdevsdir-sb" : `mongodb://localhost:27017/phdevsdir`; 31 | 32 | mongoose.connect( 33 | environment === "production" ? online_DB_uri : local_DB_uri, 34 | { 35 | useMongoClient: true 36 | }, 37 | (err, db) => { 38 | if (err) { 39 | console.log("Couldn't connect to database"); 40 | } else { 41 | console.log(`Connected To ${environment} Database`); 42 | } 43 | } 44 | ); 45 | 46 | app.use(logger("dev")); 47 | app.use(helmet()); 48 | app.disable("x-powered-by"); 49 | app.use(cookieParser()); 50 | app.use(bodyParser.json()); 51 | app.use(bodyParser.urlencoded({ extended: true })); 52 | process.env.PWD = process.cwd(); 53 | app.use(express.static(path.join(process.env.PWD, "public"))); 54 | 55 | const api = express.Router(); 56 | require("./routes/index.js")(api); 57 | app.get('/', (req, res) => { 58 | res.sendFile(path.join(__dirname, '../client/index.html')); 59 | }); 60 | app.get('/dist/*', (req, res) => { 61 | res.sendFile(path.join(__dirname, `../client/${req.originalUrl}`)); 62 | }); 63 | app.use("/api/v1", api); 64 | app.get("/isAlive", (req, res) => { 65 | res.writeHead(200, {'Content-Type': 'text/plain'}); 66 | res.end('Server is Up!\n'); 67 | }); 68 | 69 | 70 | /** 71 | * Serve API 72 | * Module dependencies. 73 | */ 74 | 75 | /** 76 | * Get port from environment and store in Express. 77 | */ 78 | 79 | const port = normalizePort(process.env.PORT || "3000"); 80 | app.set("port", port); 81 | 82 | /** 83 | * Create HTTP server. 84 | */ 85 | 86 | const server = http.createServer(app); 87 | const io = socketio(server); 88 | /** 89 | * Listen on provided port, on all network interfaces. 90 | */ 91 | 92 | server.listen(port, function() { 93 | console.log("listening on port " + port); 94 | }); 95 | server.on("error", onError); 96 | server.on("listening", onListening); 97 | 98 | /** 99 | * Normalize a port into a number, string, or false. 100 | */ 101 | 102 | function normalizePort(val) { 103 | const port = parseInt(val, 10); 104 | 105 | if (isNaN(port)) { 106 | // named pipe 107 | return val; 108 | } 109 | 110 | if (port >= 0) { 111 | // port number 112 | return port; 113 | } 114 | 115 | return false; 116 | } 117 | 118 | /** 119 | * Event listener for HTTP server "error" event. 120 | */ 121 | 122 | function onError(error) { 123 | if (error.syscall !== "listen") { 124 | throw error; 125 | } 126 | 127 | const bind = typeof port === "string" ? "Pipe " + port : "Port " + port; 128 | 129 | // handle specific listen errors with friendly messages 130 | switch (error.code) { 131 | case "EACCES": 132 | console.error(bind + " requires elevated privileges"); 133 | process.exit(1); 134 | break; 135 | case "EADDRINUSE": 136 | console.error(bind + " is already in use"); 137 | process.exit(1); 138 | break; 139 | default: 140 | throw error; 141 | } 142 | } 143 | 144 | /** 145 | * Event listener for HTTP server "listening" event. 146 | */ 147 | const debug = require("debug"); 148 | function onListening() { 149 | const addr = server.address(); 150 | const bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port; 151 | debug("Listening on " + bind); 152 | } 153 | 154 | module.exports = app; // tests 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PH Developers Directory 2 | [![CircleCI](https://circleci.com/gh/PHDevsConnect/phdevsdir/tree/master.svg?style=svg)](https://circleci.com/gh/PHDevsConnect/phdevsdir/tree/master) 3 | 4 | 5 | A Directory app for web devs in Port Harcourt, Nigeria. Built with React, Express and MongoDB 6 | 7 | # API DOCS 8 | 9 | `GET` `https://phdevsdir.herokuapp.com/api/v1/developers` - display all developers 10 | `POST` `https://phdevsdir.herokuapp.com/api/v1/developers` - create a new developer 11 | Params 12 | 13 | - `first_name` (String) `required`, 14 | - `last_name` (String) `required` 15 | - `email` (String) `required` 16 | - `stack` (Comma Delimited String) `optional` 17 | - `github_url` (String) `required` 18 | 19 | # Using the API 20 | Here are some examples to aid in using the API. This API currently makes use of one endpoint (`/api/v1/developers`), with two HTTP verbs attached to it (GET & POST). 21 | 22 | ### JavaScript 23 | ```js 24 | 25 | // using XMLHttpRequest 26 | 27 | // GET 28 | let request = new XMLHttpRequest(); 29 | request.open('Get', "https://phdevsdir.herokuapp.com/api/v1/developers"); 30 | request.send(); 31 | 32 | request.onreadystatechange = (e) => { 33 | if(request.readyState == 4 && request.status == 200) { 34 | // request is successful, lets party 35 | let response = JSON.parse(request.responseText); 36 | console.log(response); 37 | } 38 | } 39 | 40 | // POST 41 | let request = new XMLHttpRequest(); 42 | request.open('POST', 'https://phdevsdir.herokuapp.com/api/v1/developers'); 43 | let params = 'params=value'; 44 | 45 | request.setRequestHeader('Content-Type', 'application/json'); // application/x-www-form-urlencoded, etc 46 | 47 | request.onreadystatechange = (e) => { 48 | if(request.readyState == 4 && request.status == 200) { 49 | let response = JSON.parse(request.responseText); 50 | console.log(response); 51 | } 52 | } 53 | request.send(params); 54 | 55 | // Using axios (its easy af!) 56 | axios.get('https://phdevsdir.herokuapp.com/api/v1/developers') 57 | .then( (response) => { 58 | console.log(response); 59 | }) 60 | .catch( (err) => { 61 | console.log(err); 62 | }); 63 | 64 | axios.post('https://phdevsdir.herokuapp.com/api/v1/developers', { 65 | first_name: 'John', 66 | second_name: 'Doe' 67 | }) 68 | .then( (response) => { 69 | console.log(response); 70 | }) 71 | .catch( (err) => { 72 | console.log(err); 73 | }); 74 | 75 | ``` 76 | ### PHP 77 | ```php 78 | send(); 83 | if ($r->getResponseCode() == 200) { 84 | $response = $r->getResponseBody(); 85 | } 86 | } catch (HttpException $ex) { 87 | echo $ex; 88 | } 89 | 90 | // POST 91 | $r = new HttpRequest('https://phdevsdir.herokuapp.com/api/v1/developers', HttpRequest::METH_POST); 92 | $r->addPostFields(['first_name' => 'john', 'last_name' => 'doe']); 93 | try { 94 | echo $r->send()->getBody(); 95 | } catch (HttpException $ex) { 96 | echo $ex; 97 | } 98 | ?> 99 | ``` 100 | ### Go 101 | ```go 102 | func main() { 103 | // GET 104 | request, err := http.Get("https://phdevsdir.herokuapp.com/api/v1/developers") 105 | if err != nil { 106 | log.Println(err) 107 | } 108 | 109 | defer request.Body.Close() 110 | 111 | requestBytes, err := ioutil.ReadAll(request.Body) 112 | if err != nil { 113 | log.Println(err) 114 | } 115 | response := string(bodyBytes) 116 | log.Print(response) 117 | 118 | // POST 119 | body := []bytes("firstname=john&lastname=doe") 120 | req, err := http.Post("https://phdevsdir.herokuapp.com/api/v1/developers", "body/type", bytes.NewBuffer(body)) 121 | 122 | if err != nil { 123 | log.Println(err) 124 | } 125 | defer req.Body.Close() 126 | bodyBytes, err := ioutil.ReadAll(res.Body) 127 | if err != nil { 128 | log.Println(err) 129 | } 130 | res := string(bodyBytes) 131 | } 132 | ``` 133 | ### Python 134 | ```python 135 | import requests 136 | import json 137 | 138 | // GET 139 | r = requests.get("https://phdevsdir.herokuapp.com/api/v1/developers") 140 | print r.content 141 | 142 | // POST 143 | url = "https://phdevsdir.herokuapp.com/api/v1/developers" 144 | payload = {'first_name': 'John', 'last_name': 'Doe'} 145 | response = requests.post(url, data=json.dumps(payload)) 146 | print(response.text) 147 | ``` 148 | 149 | # Contributing 150 | - Fork the repo 151 | - Make your changes 152 | - Create a PR 153 | - Create an Issue for feature requests 154 | 155 | # Using Postman to test routes 156 | - Install Postman (Google Chrome Required) https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en or Download the [Desktop Client](http://getpostman.com) 157 | - Launch the app from chrome://apps 158 | - Paste the app link in the url bar, set the request method (POST, GET, PUT, UPDATE, etc) 159 | - Hit "send" 160 | - New to POSTMAN? Check tuturials on docs/tutorials.md --------------------------------------------------------------------------------