├── .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 | [](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
--------------------------------------------------------------------------------