├── .travis.yml ├── .babelrc ├── src ├── client │ ├── components │ │ ├── Tools │ │ │ ├── files │ │ │ │ ├── babel.png │ │ │ │ ├── mocha.png │ │ │ │ ├── redux.png │ │ │ │ ├── reactjs.png │ │ │ │ ├── webpack.png │ │ │ │ └── bootstrap.png │ │ │ └── index.es │ │ ├── TopImage │ │ │ ├── files │ │ │ │ └── nyc.jpg │ │ │ └── index.es │ │ ├── Footer.react.es │ │ ├── Items.react.es │ │ ├── AddItem.react.es │ │ ├── Modals │ │ │ └── AddPhoneModal.react.es │ │ └── Header.react.es │ ├── constants │ │ └── ActionTypes │ │ │ ├── Computers.es │ │ │ ├── Items.es │ │ │ ├── Example.es │ │ │ └── Phones.es │ ├── actions │ │ ├── items.es │ │ ├── exampleActions.es │ │ ├── computers.es │ │ └── phones.es │ ├── styles │ │ ├── components.scss │ │ ├── index.scss │ │ └── header.scss │ ├── reducers │ │ ├── computers.es │ │ ├── index.es │ │ ├── example.es │ │ ├── __tests__ │ │ │ ├── example-reducer.spec.js │ │ │ ├── computers-reducer.spec.js │ │ │ ├── items-reducer.spec.js │ │ │ └── phones-reducer.spec.js │ │ ├── phones.es │ │ └── items.es │ ├── containers │ │ ├── AppContainer.react.es │ │ ├── HomeContainer.react.es │ │ ├── ExampleContainer.react.es │ │ ├── ListContainer.react.es │ │ ├── PhonesContainer.react.es │ │ └── ComputersContainer.react.es │ ├── index.es │ ├── routes.es │ ├── store │ │ └── configureStore.es │ └── utils │ │ └── request.es └── server │ ├── index.js │ ├── helpers │ ├── __tests__ │ │ └── example.spec.js │ └── hookFunc.js │ ├── routes │ └── statics.js │ ├── models │ ├── log.js │ ├── func.js │ ├── index.js │ ├── printers.js │ ├── phones.js │ └── computers.js │ ├── views │ └── index.ejs │ ├── controllers │ ├── phones.js │ ├── printers.js │ ├── computers.js │ └── common.js │ ├── middleware │ ├── server.js │ ├── mongo.js │ └── express.js │ ├── app.js │ └── schema │ ├── printerType.js │ ├── phoneType.js │ ├── computerType.js │ └── schema.js ├── TODO.md ├── init_data ├── getRandomInt.js ├── generator.js ├── printers.js ├── phones.js └── computers.js ├── .gitignore ├── env ├── development.json └── index.js ├── mocha_settings ├── client-inject.js └── server-inject.js ├── webpack.js ├── README.md ├── webpack ├── utils │ └── app-server.js └── webpack.config.js ├── init_db.js └── package.json /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.3.1 -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /src/client/components/Tools/files/babel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankouskia/Shop/HEAD/src/client/components/Tools/files/babel.png -------------------------------------------------------------------------------- /src/client/components/Tools/files/mocha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankouskia/Shop/HEAD/src/client/components/Tools/files/mocha.png -------------------------------------------------------------------------------- /src/client/components/Tools/files/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankouskia/Shop/HEAD/src/client/components/Tools/files/redux.png -------------------------------------------------------------------------------- /src/client/components/Tools/files/reactjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankouskia/Shop/HEAD/src/client/components/Tools/files/reactjs.png -------------------------------------------------------------------------------- /src/client/components/Tools/files/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankouskia/Shop/HEAD/src/client/components/Tools/files/webpack.png -------------------------------------------------------------------------------- /src/client/components/TopImage/files/nyc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankouskia/Shop/HEAD/src/client/components/TopImage/files/nyc.jpg -------------------------------------------------------------------------------- /src/client/constants/ActionTypes/Computers.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | GET_ALL_COMPUTERS: 'GET_ALL_COMPUTERS' 5 | }; 6 | -------------------------------------------------------------------------------- /src/client/components/Tools/files/bootstrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yankouskia/Shop/HEAD/src/client/components/Tools/files/bootstrap.png -------------------------------------------------------------------------------- /src/client/constants/ActionTypes/Items.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | ADD_ITEM: 'ADD_ITEM', 5 | DELETE_ITEM: 'DELETE_ITEM' 6 | }; 7 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('babel-core/register'); 4 | 5 | delete process.env.BROWSER; 6 | 7 | require('./app.js').start(); 8 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Compare products 2 | Search products 3 | Getting data and present it in table 4 | Db logs 5 | Db hooks 6 | Db error handling 7 | Create frame in css 8 | -------------------------------------------------------------------------------- /src/client/constants/ActionTypes/Example.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | INCREMENT_COUNTER: 'INCREMENT_COUNTER', 5 | DECREMENT_COUNTER: 'DECREMENT_COUNTER' 6 | }; 7 | -------------------------------------------------------------------------------- /src/client/constants/ActionTypes/Phones.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | ADD_PHONE: 'ADD_PHONE', 5 | DELETE_PHONE: 'DELETE_PHONE', 6 | GET_ALL_PHONES: 'GET_ALL_PHONES' 7 | }; 8 | -------------------------------------------------------------------------------- /src/server/helpers/__tests__/example.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Example server test', () => { 4 | 5 | it('should calculate 1+1', () => { 6 | expect(1+1).to.be.equal(2); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /init_data/getRandomInt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function randomInteger(min, max) { 4 | var rand = min - 0.5 + Math.random() * (max - min + 1) 5 | rand = Math.round(rand); 6 | return rand; 7 | }; 8 | -------------------------------------------------------------------------------- /src/server/routes/statics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var router = express.Router(); 5 | 6 | router.use('/', function (req, res) { 7 | res.render('index'); 8 | }); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # temporary files 2 | *~ 3 | 4 | # apt-cacher-ng and sinopia caches 5 | caches/ 6 | 7 | # log files 8 | *.log 9 | 10 | # npm modules 11 | node_modules/ 12 | 13 | # current client build 14 | client/build/ 15 | 16 | # npm errors file 17 | npm-debug.log 18 | -------------------------------------------------------------------------------- /env/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongo": { 3 | "db": "Shop", 4 | "host": "localhost", 5 | "port": 27017, 6 | "options": { 7 | "safe": true 8 | } 9 | }, 10 | "express": { 11 | "port": 3003 12 | }, 13 | "app": { 14 | "title": "Shop", 15 | "api": "/api/v1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/server/models/log.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import mongoose from 'mongoose'; 4 | 5 | let LogShema = new mongoose.Schema({ 6 | info: { type: String, required: true, default: 'no data, but inserting was' } 7 | }, { 8 | versionKey: false 9 | }); 10 | 11 | export default mongoose.model('Log', LogShema); 12 | -------------------------------------------------------------------------------- /src/server/models/func.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import mongoose from 'mongoose'; 4 | 5 | let FunctionSchema = new mongoose.Schema({ 6 | func: { type: String, required: true, default: 'function() {return 0;}' }, 7 | }, { 8 | versionKey: false 9 | }); 10 | 11 | export default mongoose.model('Func', FunctionSchema); 12 | -------------------------------------------------------------------------------- /src/server/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Func from './func'; 4 | import Log from './log'; 5 | import Phones from './phones'; 6 | import Computers from './computers'; 7 | import Printers from './printers'; 8 | 9 | export default { 10 | Func, 11 | Log, 12 | Phones, 13 | Computers, 14 | Printers 15 | } 16 | -------------------------------------------------------------------------------- /src/client/actions/items.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import types from 'constants/ActionTypes/Items'; 4 | 5 | export function addItem(fields) { 6 | return { 7 | type: types.ADD_ITEM, 8 | fields, 9 | }; 10 | } 11 | 12 | export function delItem(index) { 13 | return { 14 | type: types.DELETE_ITEM, 15 | index, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Shop 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/server/controllers/phones.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import PhoneModel from '../models/phones'; 4 | import commonCtrl from './common'; 5 | 6 | const getAll = (mark) => commonCtrl.getAll(PhoneModel, mark); 7 | 8 | const add = (phone) => commonCtrl.add(PhoneModel, phone); 9 | 10 | export default { 11 | getAll, 12 | add 13 | }; 14 | -------------------------------------------------------------------------------- /src/server/controllers/printers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import PrinterModel from '../models/printers'; 4 | import commonCtrl from './common'; 5 | 6 | const getAll = (mark) => commonCtrl.getAll(PrinterModel, mark); 7 | 8 | const add = (printer) => commonCtrl.add(PrinterModel, printer); 9 | 10 | export default { 11 | getAll, 12 | add 13 | }; 14 | -------------------------------------------------------------------------------- /mocha_settings/client-inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.NODE_PATH = 'src/client'; 4 | 5 | require('module').Module._initPaths(); 6 | 7 | import chai from 'chai'; 8 | import chaiSubSet from 'chai-subset'; 9 | import sinonChai from 'sinon-chai'; 10 | 11 | chai.use(sinonChai); 12 | chai.use(chaiSubSet); 13 | 14 | global.expect = chai.expect; 15 | -------------------------------------------------------------------------------- /mocha_settings/server-inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.NODE_PATH = 'src/server'; 4 | 5 | require('module').Module._initPaths(); 6 | 7 | import chai from 'chai'; 8 | import chaiSubSet from 'chai-subset'; 9 | import sinonChai from 'sinon-chai'; 10 | 11 | chai.use(sinonChai); 12 | chai.use(chaiSubSet); 13 | 14 | global.expect = chai.expect; 15 | -------------------------------------------------------------------------------- /src/server/controllers/computers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import ComputerModel from '../models/computers'; 4 | import commonCtrl from './common'; 5 | 6 | const getAll = (mark) => commonCtrl.getAll(ComputerModel, mark); 7 | 8 | const add = (computer) => commonCtrl.add(ComputerModel, computer); 9 | 10 | export default { 11 | getAll, 12 | add 13 | }; 14 | -------------------------------------------------------------------------------- /src/client/styles/components.scss: -------------------------------------------------------------------------------- 1 | .header__search-input { 2 | margin-right: 5px; 3 | } 4 | 5 | .footer { 6 | position:fixed; 7 | left:0px; 8 | bottom:0px; 9 | height:33px; 10 | width:100%; 11 | background:#999; 12 | background-color: #D8D8D8; 13 | } 14 | 15 | .footer__star-button { 16 | margin-left: 10px; 17 | margin-right: 10px; 18 | } -------------------------------------------------------------------------------- /src/server/middleware/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Promise from 'bluebird'; 4 | import expressConfig from './express'; 5 | 6 | export default (app) => { 7 | return new Promise((resolve) => { 8 | expressConfig(app); 9 | 10 | app.listen(app.get('port'), () => { 11 | console.log('START LISTEN PORT - ', app.get('port')); 12 | resolve(); 13 | }); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/client/actions/exampleActions.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import types from 'constants/ActionTypes/Example'; 4 | import request from 'utils/request'; 5 | 6 | export function increment() { 7 | return dispatch => { 8 | dispatch({ type: types.INCREMENT_COUNTER }); 9 | } 10 | return { type: INCREMENT_COUNTER }; 11 | } 12 | 13 | export function decrement() { 14 | return (dispatch) => { 15 | dispatch({ type: types.DECREMENT_COUNTER }); 16 | }; 17 | } -------------------------------------------------------------------------------- /init_data/generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var getRandomInt = require('./getRandomInt'); 4 | 5 | module.exports = function(model, number) { 6 | var items = [], 7 | transformed; 8 | 9 | for(var i = 0; i < number; i++) { 10 | transformed = {}; 11 | Object.keys(model).forEach(function(key) { 12 | var randIndex = getRandomInt(0, model[key].length - 1); 13 | transformed[key] = model[key][randIndex]; 14 | }); 15 | items.push(transformed); 16 | } 17 | return items; 18 | } 19 | -------------------------------------------------------------------------------- /src/client/reducers/computers.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import types from 'constants/ActionTypes/Computers'; 4 | 5 | const initialState = { 6 | computers: [] 7 | }; 8 | 9 | export function computers(state = initialState, action) { 10 | switch (action.type) { 11 | case types.GET_ALL_COMPUTERS: 12 | return { 13 | ...state, 14 | computers: action.computers, 15 | }; 16 | 17 | default: 18 | return state; 19 | } 20 | } -------------------------------------------------------------------------------- /src/client/reducers/index.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { combineReducers } from 'redux'; 4 | import { reducer as formReducer } from 'redux-form'; 5 | import { items } from './items'; 6 | import { phones } from './phones'; 7 | import { example } from './example'; 8 | import { computers } from './computers'; 9 | 10 | const rootReducer = combineReducers({ 11 | form: formReducer, 12 | /* your reducers */ 13 | items, 14 | phones, 15 | example, 16 | computers 17 | }); 18 | 19 | export default rootReducer; 20 | -------------------------------------------------------------------------------- /src/client/styles/index.scss: -------------------------------------------------------------------------------- 1 | $icon-font-path: '~bootstrap-sass/assets/fonts/bootstrap/'; 2 | @import "~bootstrap-sass/assets/stylesheets/_bootstrap.scss"; 3 | @import './header.scss'; 4 | @import './components.scss'; 5 | 6 | .btn { 7 | &:active:focus, &:focus { 8 | outline: none; 9 | } 10 | } 11 | 12 | .btn-default 13 | { 14 | &:active:focus 15 | { 16 | outline: none; 17 | background-color: darken( #fff, 10%); 18 | } 19 | &:focus 20 | { 21 | outline: none; 22 | background-color: #fff; 23 | } 24 | } -------------------------------------------------------------------------------- /src/client/containers/AppContainer.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import Header from 'components/Header.react'; 5 | import Footer from 'components/Footer.react'; 6 | 7 | export default class App extends Component { 8 | static propTypes = { 9 | children: React.PropTypes.any, 10 | }; 11 | 12 | render() { 13 | return ( 14 |
15 |
16 | {this.props.children} 17 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/client/reducers/example.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import types from 'constants/ActionTypes/Example'; 4 | 5 | const initialState = { 6 | counter: 0, 7 | }; 8 | 9 | export function example( state = initialState, action ) { 10 | switch (action.type) { 11 | case types.INCREMENT_COUNTER: 12 | return { 13 | ...state, 14 | counter: state.counter + 1 15 | }; 16 | 17 | case types.DECREMENT_COUNTER: 18 | return { 19 | ...state, 20 | counter: state.counter - 1 21 | }; 22 | 23 | default: 24 | return state; 25 | } 26 | } -------------------------------------------------------------------------------- /src/client/index.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { Provider } from 'react-redux'; 6 | import { Router, useRouterHistory } from 'react-router'; 7 | import { createHashHistory } from 'history'; 8 | import configureStore from './store/configureStore'; 9 | import routes from './routes'; 10 | import 'styles/index.scss'; 11 | 12 | const history = useRouterHistory(createHashHistory)({ queryKey: false }); 13 | const store = configureStore(); 14 | 15 | ReactDOM.render( 16 | 17 | 18 | , 19 | document.getElementById('react-container') 20 | ); 21 | -------------------------------------------------------------------------------- /src/client/components/Footer.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { Button, Glyphicon } from 'react-bootstrap'; 5 | 6 | export default class Footer extends Component { 7 | 8 | render() { 9 | return ( 10 |
11 |
12 | 18 |
19 |
20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/server/helpers/hookFunc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Log from '../models/log'; 4 | import Func from '../models/func'; 5 | 6 | export default function(next, done, self, modelName) { 7 | let currentDate = new Date(); 8 | let date = `${currentDate.getDay()}.${currentDate.getMonth()}.${currentDate.getFullYear()}`; 9 | let time = `${currentDate.getHours()}:${currentDate.getMinutes()}:${currentDate.getSeconds()}`; 10 | let log = new Log({ 11 | info: `In collection: "${modelName}" was written new item on ${date} in ${time}` 12 | }) 13 | 14 | Func.findOne({}, (err, res) => { 15 | eval(res); 16 | log.save(() => { 17 | next(); 18 | }) 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /init_data/printers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var generator = require('./generator'); 4 | 5 | var printerModel = { 6 | mark: ['HP', 'Canon', 'Epson', 'Samsung'], 7 | model: ['L800', 'Mita FS-1040', '111SU', 'Mita FS-1020MFP', 'LaserJet Pro MFP M177fw'], 8 | color: ['black', 'white', 'green', 'grey', 'yellow', 'blue', 'red', 'orange', 'violet', 'purple'], 9 | wifi: ['802.11ac', '802.11a', '802.11c'], 10 | isMFP: [true, false], 11 | isBlackWhite: [true, false], 12 | pageVelocityPerMinute: [22, 23, 24, 25, 26, 30, 33, 36, 37, 40, 42, 45, 60], 13 | printTechnology: ['jet', 'laser'], 14 | price: [80, 110, 130, 140, 155, 160, 187, 210, 220, 254, 270, 310, 340], 15 | }; 16 | 17 | module.exports = generator(printerModel, 15); 18 | -------------------------------------------------------------------------------- /src/server/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import express from 'express'; 4 | import mongoMiddleware from './middleware/mongo'; 5 | import serverMiddleware from './middleware/server'; 6 | import Promise from 'bluebird'; 7 | 8 | const app = express(); 9 | let isAppStarted = false; 10 | 11 | app.start = () => { 12 | if(!isAppStarted) { 13 | isAppStarted = true; 14 | Promise.all([ 15 | serverMiddleware(app), 16 | mongoMiddleware(app) 17 | ]).then(() => { 18 | if (process.send) { 19 | process.send('online'); 20 | } 21 | }, (error) => { 22 | console.error(error); 23 | }); 24 | } 25 | }; 26 | 27 | if (!module.parent) { 28 | try { 29 | app.start(); 30 | } catch(e) {} 31 | } 32 | 33 | module.exports = app; 34 | -------------------------------------------------------------------------------- /src/client/containers/HomeContainer.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { 5 | Accordion, 6 | Panel 7 | } from 'react-bootstrap'; 8 | 9 | import { TopImage } from 'components/TopImage'; 10 | 11 | export default class Home extends Component { 12 | render() { 13 | return ( 14 |
15 |
16 | 17 | 18 | All you want :) 19 | 20 | 21 | Best personal, the lowest prices 22 | 23 | 24 | Best programmers 25 | 26 | 27 |
28 | 29 |
30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/client/routes.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Route, IndexRoute } from 'react-router'; 5 | 6 | /* containers */ 7 | import App from 'containers/AppContainer.react'; 8 | import Home from 'containers/HomeContainer.react'; 9 | import List from 'containers/ListContainer.react'; 10 | import Phones from 'containers/PhonesContainer.react'; 11 | import Example from 'containers/ExampleContainer.react'; 12 | import Computers from 'containers/ComputersContainer.react'; 13 | 14 | export default ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /webpack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Webpack = require('webpack'), 4 | WebpackDevServer = require('webpack-dev-server'), 5 | webpackConfig = Webpack(require('./webpack/webpack.config.js')), 6 | server = { 7 | port: 3000, 8 | host: 'localhost', 9 | 10 | options: { 11 | hot: true, 12 | 13 | publicPath: '/', 14 | stats: { 15 | colors: true 16 | }, 17 | 18 | noInfo: true, 19 | proxy: [{ 20 | path: '*', 21 | target: 'http://localhost:3003' 22 | }], 23 | historyApiFallback: true 24 | } 25 | }, 26 | devServer; 27 | 28 | devServer = new WebpackDevServer(webpackConfig, server.options); 29 | 30 | devServer.listen(server.port, server.host, function () { 31 | console.log('\nstarting app\n'); 32 | }); 33 | -------------------------------------------------------------------------------- /src/client/actions/computers.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import request from 'utils/request'; 4 | import types from 'constants/ActionTypes/Computers'; 5 | 6 | export function getAllComputers(mark = '') { 7 | const searchByMark = mark ? `(mark:"${mark}")` : '', 8 | query = `query{computers ${searchByMark} { 9 | mark 10 | model 11 | color 12 | wifi 13 | isLaptop 14 | diagonal 15 | coresNumber 16 | usb2 17 | usb3 18 | ram 19 | memory 20 | videocard 21 | videomemory 22 | processor 23 | operatingSystem 24 | price 25 | }}`; 26 | 27 | return dispatch => { 28 | request.get('/', {query}).then(({data}) => { 29 | dispatch({ type: types.GET_ALL_COMPUTERS, computers: data.computers }); 30 | }); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/client/reducers/__tests__/example-reducer.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { example } from '../example'; 4 | 5 | describe('Example reducer:', () => { 6 | 7 | const initialState = { 8 | counter: 0 9 | }; 10 | 11 | it('should return the initial state', () => { 12 | expect(example(initialState, {})).to.deep.equal(initialState); 13 | }); 14 | 15 | it('should handle INCREMENT_COUNTER', () => { 16 | const expectedState = { 17 | counter: 1 18 | }; 19 | 20 | expect(example(initialState, { 21 | type: 'INCREMENT_COUNTER' 22 | })).to.deep.equal(expectedState); 23 | }); 24 | 25 | it('should handle DECREMENT_COUNTER', () => { 26 | const expectedState = { 27 | counter: -1 28 | }; 29 | 30 | expect(example(initialState, { 31 | type: 'DECREMENT_COUNTER' 32 | })).to.deep.equal(expectedState); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shop [![Build Status](https://travis-ci.org/yankouskia/Shop.svg?branch=master)](https://travis-ci.org/yankouskia/Shop) 2 | 3 | ## Preparation 4 | 5 | > Install nodejs 6 | 7 | > Install mongodb 8 | 9 | ## Run project 10 | 11 | ```sh 12 | npm install 13 | npm run add-db (for adding initial data) 14 | npm start 15 | ``` 16 | 17 | ## Run tests 18 | 19 | ```sh 20 | npm test 21 | ``` 22 | 23 | ### Fill db 24 | 25 | ```sh 26 | npm run add-db 27 | ``` 28 | 29 | ### Technologies 30 | 31 | - [x] React 32 | - [x] Redux 33 | - [x] GraphQL 34 | - [x] Immutable 35 | - [x] Babel 6 36 | - [x] Webpack 37 | - [x] Webpack-dev-server 38 | - [x] express 39 | - [x] Mongoose 40 | - [x] react-bootstrap 41 | - [x] react-redux 42 | - [x] sass 43 | - [x] and other ... 44 | 45 | 46 | ### About 47 | * Project represents internet shop. It can easy help people to understand above mentioned technologies. Or just use it as boilerplate in their projects. 48 | 49 | **_Open for PRs_** :+1: 50 | 51 | -------------------------------------------------------------------------------- /src/client/store/configureStore.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { applyMiddleware, createStore } from 'redux'; 4 | import thunkMiddleware from 'redux-thunk'; 5 | import createLogger from 'redux-logger'; 6 | import rootReducer from '../reducers'; 7 | 8 | export default function configureStore(initialState) { 9 | const logger = createLogger({ 10 | collapsed: true, 11 | predicate: () => process.env.NODE_ENV === `development` // eslint-disable-line no-unused-vars 12 | }); 13 | 14 | const middleware = applyMiddleware(thunkMiddleware, logger); 15 | 16 | const store = middleware(createStore)(rootReducer, initialState); 17 | 18 | if (module.hot) { 19 | // Enable Webpack hot module replacement for reducers 20 | module.hot.accept('../reducers', () => { 21 | const nextRootReducer = require('../reducers').default; 22 | store.replaceReducer(nextRootReducer); 23 | }); 24 | } 25 | 26 | return store; 27 | } 28 | -------------------------------------------------------------------------------- /env/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nconf = require('nconf'); 4 | var path = require('path'); 5 | var mDockerHost = process.env.IPLN_MONGO_PORT_27017_TCP_ADDR; 6 | var mDockerPort = process.env.IPLN_MONGO_PORT_27017_TCP_PORT; 7 | 8 | if(!process.env.SHOP) { 9 | process.env.SHOP = 'development'; 10 | } 11 | 12 | var env = process.env.SHOP; 13 | 14 | if (env !== undefined) { 15 | env = env.trim(); 16 | } else { 17 | env = 'development'; 18 | } 19 | 20 | nconf 21 | .argv() 22 | .env() 23 | .file({ 24 | file: path.join(__dirname, env + '.json') 25 | }); 26 | 27 | if(mDockerHost !== undefined && mDockerPort !== undefined) { 28 | nconf.set('mongo:host', mDockerHost); 29 | nconf.set('mongo:port', mDockerPort); 30 | } 31 | 32 | var mongoURI = 'mongodb://' + 33 | nconf.get('mongo:host') + ':' + 34 | nconf.get('mongo:port') + '/' + 35 | nconf.get('mongo:db'); 36 | 37 | nconf.set('mongo:uri', mongoURI); 38 | 39 | module.exports = nconf; 40 | -------------------------------------------------------------------------------- /init_data/phones.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var generator = require('./generator'); 4 | 5 | var phoneModel = { 6 | mark: ['iPhone', 'Samsung', 'HTC', 'Huawey', 'Lenovo', 'Sony', 'Nokia', 'Xaomi', 'LG', 'ZTE'], 7 | model: ['6s', '5s', 'Galaxy', 'P70-A', 'Light Dual', 'Vibe', 'P1M', '650', 'Grand Prime', 'MX5'], 8 | color: ['black', 'white', 'green', 'grey', 'yellow', 'blue', 'red', 'orange', 'violet', 'purple'], 9 | wifi: ['802.11ac', '802.11a', '802.11c'], 10 | gps: [true, false], 11 | coresNumber: [1, 2, 4, 8], 12 | ram: [1, 2, 4, 8], 13 | memory: [2, 4, 8, 16, 32, 64], 14 | camera: [5, 8, 10, 12, 13, 15, 18], 15 | diagonal: [4, 4.1, 4.2, 4.4, 4.5, 4.7, 4.8, 4.9, 5.0, 5.1, 5.2, 5.3, 5.5, 5.7, 6.0, 6.1, 6.3], 16 | operatingSystem: ['Android', 'iOS', 'Window Phone'], 17 | price: [200, 220, 240, 260, 280, 300, 330, 360, 390, 420, 450, 480, 500, 540, 560, 580, 600, 620, 660, 710, 730, 800] 18 | }; 19 | 20 | module.exports = generator(phoneModel, 20); 21 | -------------------------------------------------------------------------------- /src/client/reducers/__tests__/computers-reducer.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { computers } from '../computers'; 4 | 5 | describe('Computers reducer:', () => { 6 | 7 | const initialState = { 8 | computers: [] 9 | }; 10 | 11 | it('should return the initial state', () => { 12 | expect(computers(initialState, {})).to.deep.equal(initialState); 13 | }); 14 | 15 | it('should handle GET_ALL_COMPUTERS', () => { 16 | const expectedState = { 17 | computers: [{ 18 | name: 'macbook' 19 | }, { 20 | name: 'asus' 21 | }] 22 | }; 23 | 24 | const computersToGet = [{ 25 | name: 'macbook' 26 | }, { 27 | name: 'asus' 28 | }]; 29 | 30 | expect(computers(initialState, { 31 | type: 'GET_ALL_COMPUTERS', 32 | computers: computersToGet, 33 | })).to.deep.equal(expectedState); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/client/reducers/phones.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import types from 'constants/ActionTypes/Phones'; 4 | 5 | const initialState = { 6 | phones: [] 7 | }; 8 | 9 | export function phones(state = initialState, action) { 10 | switch (action.type) { 11 | case types.ADD_PHONE: 12 | return { 13 | ...state, 14 | phones: [ 15 | ...state.phones, 16 | action.phone 17 | ] 18 | }; 19 | 20 | case types.DELETE_PHONE: 21 | return { 22 | ...state, 23 | phones: [ 24 | ...state.phones.slice(0, action.index), 25 | ...state.phones.slice(+action.index + 1), 26 | ] 27 | }; 28 | 29 | case types.GET_ALL_PHONES: 30 | return { 31 | ...state, 32 | phones: action.phones 33 | }; 34 | 35 | default: 36 | return state; 37 | } 38 | } -------------------------------------------------------------------------------- /src/client/actions/phones.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import request from 'utils/request'; 4 | import types from 'constants/ActionTypes/Phones'; 5 | 6 | export function getAllPhones(mark = '') { 7 | const searchByMark = mark ? `(mark:"${mark}")` : '', 8 | query = `query{phones ${searchByMark} { 9 | mark 10 | model 11 | color 12 | wifi 13 | gps 14 | coresNumber 15 | ram 16 | memory 17 | camera 18 | diagonal 19 | operatingSystem 20 | price 21 | }}`; 22 | 23 | return dispatch => { 24 | request.get('/', {query}).then(({data}) => { 25 | dispatch({ type: types.GET_ALL_PHONES, phones: data.phones }); 26 | }); 27 | }; 28 | } 29 | 30 | export function addPhone(fields) { 31 | return { 32 | type: types.ADD_PHONE, 33 | fields, 34 | }; 35 | } 36 | 37 | export function deletePhone(index) { 38 | return { 39 | type: types.DELETE_PHONE, 40 | index, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/server/controllers/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getAll = (Model, mark) => { 4 | return new Promise((resolve, reject) => { 5 | Model.find({}, function(err, docs) { 6 | if(!err) { 7 | if (mark) { // TODO: improve working with filters, use generic for filtering by more params! 8 | mark = mark.trim().toLowerCase(); 9 | 10 | docs = docs.filter(pc => pc.mark.toLowerCase() === mark); 11 | } 12 | 13 | resolve(docs); 14 | } else { 15 | reject({error: err}); 16 | } 17 | }); 18 | }); 19 | }; 20 | 21 | const add = (Model, entity) => { 22 | return new Promise((resolve, reject) => { 23 | const model = new Model(entity); 24 | 25 | model.save(function(err, docs) { 26 | if(!err) { 27 | resolve(model); 28 | } 29 | else { 30 | reject({ error: err }); 31 | } 32 | }); 33 | }); 34 | }; 35 | 36 | export default { 37 | getAll, 38 | add 39 | }; 40 | -------------------------------------------------------------------------------- /src/client/reducers/__tests__/items-reducer.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { items } from '../items'; 4 | 5 | describe('Items reducer:', () => { 6 | 7 | const initialState = { 8 | items: [] 9 | }; 10 | 11 | it('should return the initial state', () => { 12 | expect(items(initialState, {})).to.deep.equal(initialState); 13 | }); 14 | 15 | it('should handle ADD', () => { 16 | const expectedState = { 17 | items: [{ 18 | text: 'test' 19 | }], 20 | }; 21 | const fields = { name: { value: 'test'}}; 22 | expect(items(initialState, { 23 | type: 'ADD_ITEM', 24 | fields: fields, 25 | })).to.deep.equal(expectedState); 26 | }); 27 | 28 | it('should handle DELETE', () => { 29 | const stateWithItem = { 30 | items: [{ 31 | text: 'test' 32 | }], 33 | }; 34 | expect(items(stateWithItem, { 35 | type: 'DELETE_ITEM', 36 | index: 0 37 | })).to.deep.equal(initialState); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/server/middleware/mongo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Promise from 'bluebird'; 4 | import mongoose from 'mongoose'; 5 | import async from 'async'; 6 | 7 | import env from '../../../env'; 8 | 9 | export default (app) => { 10 | return new Promise((resolve, reject) => { 11 | if (app.mongo) { 12 | mongoose.connection.close(); 13 | app.mongo = null; 14 | } 15 | 16 | if (env.get('SHOP') === 'development') { 17 | mongoose.set('debug', (collection, method, query, doc) => { 18 | console.log('mongo data: ', collection, method, query, doc); 19 | }); 20 | } 21 | 22 | mongoose.connection.once('open', () => { 23 | app.mongo = mongoose.connection; 24 | console.log('connected to mongo'); 25 | resolve(); 26 | }); 27 | 28 | mongoose.connection.on('error', (err) => { 29 | console.log('error connection'); 30 | reject(); 31 | }); 32 | 33 | 34 | mongoose.connect(env.get('mongo:uri'), env.get('mongo:options')); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /src/server/models/printers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import mongoose from 'mongoose'; 4 | import hookFunc from '../helpers/hookFunc'; 5 | 6 | const PRINTER_SCHEMA_NAME = 'Printer'; 7 | 8 | let PrinterSchema = new mongoose.Schema({ 9 | mark: { type: String, required: true, default: '' }, 10 | model: { type: String, required: true, default: '' }, 11 | color: { type: String, required: true, default: '' }, 12 | wifi: { type: String, required: true, default: '' }, 13 | isMFP: { type: Boolean, required: true, default: false}, 14 | isBlackWhite: { type: Boolean, required: true, default: true}, 15 | pageVelocityPerMinute: { type: Number, required: true, default: 0 }, 16 | printTechnology: { type: String, required: true, default: '' }, 17 | price: { type: Number, required: true, default: 0 } 18 | }, { 19 | versionKey: false 20 | }); 21 | 22 | PrinterSchema.plugin(require('mongoose-timestamp')); 23 | 24 | PrinterSchema.pre('save', function(next, done) { 25 | hookFunc(next, done, this, PRINTER_SCHEMA_NAME); 26 | }) 27 | 28 | export default mongoose.model(PRINTER_SCHEMA_NAME, PrinterSchema); 29 | -------------------------------------------------------------------------------- /src/server/models/phones.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import mongoose from 'mongoose'; 4 | import hookFunc from '../helpers/hookFunc'; 5 | 6 | const PHONE_SCHEMA_NAME = 'Phone'; 7 | 8 | let PhoneSchema = new mongoose.Schema({ 9 | mark: { type: String, required: true, default: '' }, 10 | model: { type: String, required: true, default: '' }, 11 | color: { type: String, required: true, default: '' }, 12 | wifi: { type: String, required: true, default: '' }, 13 | gps: { type: Boolean, required: true, default: false}, 14 | coresNumber: { type: Number, required: true, default: 0 }, 15 | ram: { type: Number, required: true, default: 0 }, 16 | memory: { type: Number, required: true, default: 0 }, 17 | camera: { type: Number, required: true, default: 0 }, 18 | diagonal: { type: Number, required: true, default: 0 }, 19 | operatingSystem: { type: String, required: true, default: 'Android' }, 20 | price: { type: Number, required: true, default: 0 } 21 | }, { 22 | versionKey: false 23 | }); 24 | 25 | PhoneSchema.plugin(require('mongoose-timestamp')); 26 | 27 | PhoneSchema.pre('save', function(next, done) { 28 | hookFunc(next, done, this, PHONE_SCHEMA_NAME); 29 | }) 30 | 31 | export default mongoose.model(PHONE_SCHEMA_NAME, PhoneSchema); 32 | -------------------------------------------------------------------------------- /src/client/styles/header.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | .drop-target { 6 | border: 4px dashed #FFF; 7 | } 8 | 9 | .notes-shell { 10 | display: inline-block; 11 | 12 | .note { 13 | display: flex; 14 | align-items: center; 15 | flex-direction: columns; 16 | width: 200px; 17 | height: 250px; 18 | } 19 | } 20 | 21 | .container { 22 | display: flex; 23 | 24 | .list { 25 | width: 200px; 26 | } 27 | } 28 | 29 | .notes { 30 | width: 100%; 31 | 32 | .notes-container, .add-notes { 33 | display: flex; 34 | justify-content: space-around; 35 | .note-title { 36 | border-bottom: 1px solid #000; 37 | display: flex; 38 | align-items: center; 39 | justify-content: space-around; 40 | } 41 | 42 | .note-body { 43 | display: flex; 44 | justify-content: space-around; 45 | height: 190px; 46 | 47 | .note-text { 48 | background: none; 49 | border: none; 50 | outline: none; 51 | resize: none; 52 | height: 100%; 53 | width: 100%; 54 | max-height: 180px; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /webpack/utils/app-server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var cp = require('child_process'); 4 | var path = require('path'); 5 | var watch = require('node-watch'); 6 | var opener = require('opener'); 7 | var _ = require('lodash'); 8 | 9 | var APP_PATH = path.join(__dirname, '../../src/server'); 10 | var WATCH_PATH = path.join(__dirname, '../../src/server'); 11 | 12 | var app; 13 | var isStarted; 14 | 15 | var startApp = function () { 16 | var env = _.assign({NODE_ENV: 'development', WEBPACK_SERVE: true}, process.env); 17 | app = cp.fork(APP_PATH, {env: env}); 18 | app.once('message', function(message) { 19 | if (message.match(/^online$/)) { 20 | if(!isStarted) { 21 | isStarted = true; 22 | opener('http://localhost:3000'); 23 | 24 | watch( 25 | WATCH_PATH, 26 | function(file) { 27 | console.log('restarting app'); 28 | app.kill('SIGTERM'); 29 | return startApp(); 30 | } 31 | ); 32 | } 33 | } 34 | }); 35 | }; 36 | 37 | process.on('exit', function() { 38 | app.kill('SIGTERM'); 39 | }); 40 | 41 | module.exports = function() { 42 | if(!app) { 43 | return startApp(); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /init_data/computers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var generator = require('./generator'); 4 | 5 | var computerModel = { 6 | mark: ['ASUS', 'Lenovo', 'HP', 'Apple', 'MSI', 'Acer', 'Dell', 'Toshiba', 'Sony', 'DEXP'], 7 | model: ['GX-700', '6QE-053', '9F4-5', 'DM036T', 'Zenbook Pro', 'GL552VW-DM321T', 'EeeBook', 'Surface', 'Redhost', 'MEDIA CENTER 1574', 'Mac mini', 'Mac Pro', 'Z800', 'IdeaPad', 'y580', 'optima', 'GAMER 1748'], 8 | color: ['black', 'white', 'green', 'grey', 'yellow', 'blue', 'red', 'orange', 'violet', 'purple'], 9 | wifi: ['802.11ac', '802.11a', '802.11c'], 10 | isLaptop: [true, false], 11 | diagonal: [11.1, 11.3, 13.3, 13.7, 14.0, 15.6, 17.1, 17.3, 18.6, 20.1], 12 | coresNumber: [1, 2, 4, 8, 16], 13 | usb2: [0, 1, 2, 3], 14 | usb3: [0, 1, 2, 3, 4], 15 | ram: [1, 2, 4, 8, 16, 32, 64], 16 | memory: [128, 256, 512, 1024, 2048, 4096, 8192], 17 | videocard: ['Intel HD Graphics 3000', 'NVidia GeForce GTX 640', 'Intel HD Graphics 4000', 'NVidia GeForce GTX 730', 'NVidia GeForce GTX 950'], 18 | videomemory: [1, 2, 4, 8, 16, 32, 64], 19 | processor: ['Intel', 'AMD', 'Mac'], 20 | operatingSystem: ['Windows', 'Linux', 'Mac OS X'], 21 | price: [300, 340, 380, 420, 460, 495, 510, 530, 560, 580, 640, 670, 725, 760, 780, 840, 850, 860, 920, 940, 1020, 1150, 1280, 1500] 22 | }; 23 | 24 | module.exports = generator(computerModel, 30); 25 | -------------------------------------------------------------------------------- /src/client/containers/ExampleContainer.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { PropTypes } from 'react'; 4 | import { bindActionCreators } from 'redux'; 5 | import { connect } from 'react-redux'; 6 | import * as counterActions from 'actions/exampleActions'; 7 | import { Button } from 'react-bootstrap'; 8 | 9 | function mapStateToProps(state) { 10 | return { 11 | counter: state.example.counter, 12 | }; 13 | } 14 | 15 | function mapDispatchToProps(dispatch) { 16 | return bindActionCreators({ 17 | ...counterActions, 18 | }, dispatch); 19 | } 20 | 21 | class HomeRoute extends React.Component { 22 | static propTypes: {[key: string]: Function}; 23 | 24 | render() { 25 | return ( 26 |
27 | 28 | 31 | 34 | 35 | {this.props.counter} 36 | 37 |
38 | ); 39 | } 40 | } 41 | 42 | HomeRoute.propTypes = { 43 | increment: PropTypes.func.isRequired, 44 | counter: PropTypes.number.isRequired, 45 | }; 46 | 47 | export default connect(mapStateToProps, mapDispatchToProps)(HomeRoute); 48 | -------------------------------------------------------------------------------- /src/client/utils/request.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import request from 'superagent'; 4 | 5 | const API_URL = '/api/v1'; 6 | const HEADERS = { Accept: 'application/json' }; 7 | const setPrefix = (prefix) => { 8 | return (req) => { 9 | if (req.url && req.url.indexOf(prefix) === -1) { 10 | req.url = `${prefix}${req.url}`; 11 | } 12 | 13 | return req; 14 | }; 15 | }; 16 | 17 | var builder = (httpMethod, apiMethod, params, headers=HEADERS) => { 18 | let paramsTransport = httpMethod === 'get' ? 19 | 'query' : 20 | 'send'; 21 | 22 | return new Promise((resolve, reject) => { 23 | request[httpMethod](apiMethod) 24 | .set(headers) 25 | [paramsTransport](params) 26 | .use(setPrefix(API_URL)) 27 | .end((err, res) => { 28 | if (err || !res || !res.ok) { 29 | reject(err); 30 | } else { 31 | resolve(res.body, res); 32 | } 33 | }); 34 | }); 35 | }; 36 | 37 | export default { 38 | fetch(httpMethod, apiMethod, params) { 39 | return builder(httpMethod, apiMethod, params); 40 | }, 41 | 42 | get(apiMethod, params, queueName) { 43 | return builder('get', apiMethod, params); 44 | }, 45 | 46 | post(apiMethod, params, queueName) { 47 | return builder('post', apiMethod, params); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/server/schema/printerType.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLObjectType, 3 | GraphQLBoolean, 4 | GraphQLString, 5 | GraphQLFloat, 6 | } from 'graphql'; 7 | 8 | export const PrinterType = new GraphQLObjectType({ 9 | name: 'printer', 10 | description: 'A phone.', 11 | fields: () => ({ 12 | mark: { 13 | type: GraphQLString, 14 | description: 'The mark of printer.' 15 | }, 16 | model: { 17 | type: GraphQLString, 18 | description: 'The model of printer.' 19 | }, 20 | color: { 21 | type: GraphQLString, 22 | description: 'The color of printer.' 23 | }, 24 | wifi: { 25 | type: GraphQLString, 26 | description: 'The wifi of printer.' 27 | }, 28 | isMFP: { 29 | type: GraphQLBoolean, 30 | description: 'The isMFP of printer.' 31 | }, 32 | isBlackWhite: { 33 | type: GraphQLBoolean, 34 | description: 'The isBlackWhite of printer.' 35 | }, 36 | pageVelocityPerMinute: { 37 | type: GraphQLFloat, 38 | description: 'The pageVelocityPerMinute of printer.' 39 | }, 40 | printTechnology: { 41 | type: GraphQLString, 42 | description: 'The printTechnology of printer.' 43 | }, 44 | price: { 45 | type: GraphQLFloat, 46 | description: 'The price of printer.' 47 | } 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /src/client/containers/ListContainer.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { bindActionCreators } from 'redux'; 5 | import { connect } from 'react-redux'; 6 | 7 | import Items from 'components/Items.react'; 8 | import AddItem from 'components/AddItem.react'; 9 | import Tools from 'components/Tools'; 10 | 11 | /* actions */ 12 | import * as actionCreators from 'actions/items'; 13 | 14 | 15 | @connect( 16 | state => state.items, 17 | dispatch => bindActionCreators(actionCreators, dispatch) 18 | ) 19 | export default class List extends Component { 20 | constructor(props) { 21 | super(props); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 |
28 |
29 | 30 |
31 |

32 | Shop contains technologies: 33 |

34 | 35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/server/models/computers.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | import mongoose from 'mongoose'; 5 | import hookFunc from '../helpers/hookFunc'; 6 | 7 | const COMPUTER_SCHEMA_NAME = 'Computer'; 8 | 9 | let ComputerSchema = new mongoose.Schema({ 10 | mark: { type: String, required: true, default: '' }, 11 | model: { type: String, required: true, default: '' }, 12 | color: { type: String, required: true, default: '' }, 13 | wifi: { type: String, required: true, default: '' }, 14 | isLaptop: { type: Boolean, required: true, default: true}, 15 | diagonal: { type: Number, required: true, default: 0 }, 16 | coresNumber: { type: Number, required: true, default: 0 }, 17 | usb2: { type: Number, required: true, default: 0 }, 18 | usb3: { type: Number, required: true, default: 0 }, 19 | ram: { type: Number, required: true, default: 0 }, 20 | memory: { type: Number, required: true, default: 0 }, 21 | videocard: { type: String, required: true, default: '' }, 22 | videomemory: { type: Number, required: true, default: 0 }, 23 | processor: { type: String, required: true, default: '' }, 24 | operatingSystem: { type: String, required: true, default: '' }, 25 | price: { type: Number, required: true, default: 0 } 26 | }, { 27 | versionKey: false 28 | }); 29 | 30 | ComputerSchema.plugin(require('mongoose-timestamp')); 31 | 32 | ComputerSchema.pre('save', function(next, done) { 33 | hookFunc(next, done, this, COMPUTER_SCHEMA_NAME); 34 | }) 35 | 36 | export default mongoose.model(COMPUTER_SCHEMA_NAME, ComputerSchema); 37 | -------------------------------------------------------------------------------- /src/client/components/Items.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | 5 | export default class Items extends Component { 6 | 7 | static propTypes = { 8 | items: React.PropTypes.array, 9 | delItem: React.PropTypes.func, 10 | }; 11 | 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | onDelete = (event) => { 17 | event.preventDefault(); 18 | const index = event.currentTarget.dataset.index; 19 | this.props.delItem(index); 20 | }; 21 | 22 | render() { 23 | const { items } = this.props; 24 | 25 | return ( 26 |
27 | {!items.length && Array is empty} 28 | { 29 | items.map((item, index) => 30 |
31 | 43 |
44 | ) 45 | } 46 |
47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/client/components/TopImage/index.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import LoadingOrderAnimation from 'react-loading-order-with-animation'; 5 | 6 | export class TopImage extends Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 |
16 |
17 | 23 |

24 | Welcome to our shop!!! 25 |

26 |
27 | 33 |

34 | Start spend your money! :) 35 |

36 |
37 |
38 |
39 |
40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/server/middleware/express.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path from 'path'; 4 | import helmet from 'helmet'; 5 | import hpp from 'hpp'; 6 | import bodyParser from 'body-parser'; 7 | import staticsRouter from '../routes/statics'; 8 | import env from '../../../env'; 9 | import GraphQLHTTP from 'express-graphql'; 10 | import schema from '../schema/schema'; 11 | 12 | const errorHandler = (err, req, res, next) => { 13 | let errData = err.message ? err.message : err; 14 | 15 | res.status(err.status || 500); 16 | 17 | if (req.accepts('json')) { 18 | res.json({error: true, data: errData}); 19 | } else { 20 | res.render('error', { 21 | message: err.message, 22 | error: {}, 23 | title: 'error' 24 | }); 25 | } 26 | }; 27 | 28 | export default (app) => { 29 | app.set('views', path.join(__dirname, '../views')); 30 | app.set('view engine', 'ejs'); 31 | app.set('port', env.get('express:port')); 32 | 33 | // some secure headers 34 | app.use(helmet.xssFilter()); 35 | app.use(helmet.frameguard('sameorigin')); 36 | app.use(helmet.hidePoweredBy()); 37 | app.use(helmet.ieNoOpen()); 38 | app.use(helmet.noSniff()); 39 | 40 | app.use(bodyParser.urlencoded({ 41 | extended: true 42 | })); 43 | 44 | app.use(bodyParser.json()); 45 | 46 | app.use(hpp()); 47 | app.use(env.get('app:api'), GraphQLHTTP({ 48 | schema, 49 | graphiql: true // TODO: enable only for dev 50 | }) 51 | ); 52 | app.use('/', staticsRouter); 53 | 54 | app.use(errorHandler); 55 | 56 | console.log('express configured'); 57 | }; 58 | -------------------------------------------------------------------------------- /src/client/components/AddItem.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { reduxForm } from 'redux-form'; 5 | import { reset } from 'redux-form'; 6 | 7 | class AddItem extends Component { 8 | 9 | static propTypes = { 10 | dispatch: React.PropTypes.func, 11 | fields: React.PropTypes.object.isRequired, 12 | items: React.PropTypes.array, 13 | addItem: React.PropTypes.func, 14 | }; 15 | 16 | constructor(props) { 17 | super(props); 18 | } 19 | 20 | onAdd = (event) => { 21 | if (this.props.fields.name.value) { 22 | this.props.addItem(this.props.fields); 23 | this.props.dispatch(reset('addItem')); 24 | } 25 | event.preventDefault(); 26 | }; 27 | 28 | render() { 29 | const { 30 | fields: { name }, 31 | } = this.props; 32 | 33 | return ( 34 |
35 |
36 | 42 |
43 |
44 | 47 |
48 |
49 | ); 50 | } 51 | } 52 | 53 | AddItem = reduxForm({ 54 | form: 'addItem', 55 | fields: ['name'], 56 | destroyOnUnmount: false, 57 | })(AddItem); 58 | 59 | export default AddItem; 60 | -------------------------------------------------------------------------------- /src/server/schema/phoneType.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLObjectType, 3 | GraphQLBoolean, 4 | GraphQLString, 5 | GraphQLFloat, 6 | GraphQLInt, 7 | } from 'graphql'; 8 | 9 | export const PhoneType = new GraphQLObjectType({ 10 | name: 'phone', 11 | description: 'A phone.', 12 | fields: () => ({ 13 | mark: { 14 | type: GraphQLString, 15 | description: 'The mark of phone.' 16 | }, 17 | model: { 18 | type: GraphQLString, 19 | description: 'The model of phone.' 20 | }, 21 | color: { 22 | type: GraphQLString, 23 | description: 'The color of phone.' 24 | }, 25 | wifi: { 26 | type: GraphQLString, 27 | description: 'The wifi of phone.' 28 | }, 29 | gps: { 30 | type: GraphQLBoolean, 31 | description: 'The gps of phone.' 32 | }, 33 | coresNumber: { 34 | type: GraphQLInt, 35 | description: 'The coresNumber of phone.' 36 | }, 37 | ram: { 38 | type: GraphQLInt, 39 | description: 'The ram of phone.' 40 | }, 41 | memory: { 42 | type: GraphQLInt, 43 | description: 'The memory of phone.' 44 | }, 45 | camera: { 46 | type: GraphQLInt, 47 | description: 'The camera of phone.' 48 | }, 49 | diagonal: { 50 | type: GraphQLFloat, 51 | description: 'The diagonal of phone.' 52 | }, 53 | operatingSystem: { 54 | type: GraphQLString, 55 | description: 'The operatingSystem of phone.' 56 | }, 57 | price: { 58 | type: GraphQLFloat, 59 | description: 'The price of phone.' 60 | }, 61 | }) 62 | }); 63 | -------------------------------------------------------------------------------- /src/client/reducers/items.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import types from 'constants/ActionTypes/Items'; 4 | 5 | const initialState = { 6 | items: [{ 7 | text: 'React', 8 | done: true 9 | }, { 10 | text: 'Redux', 11 | done: true 12 | }, { 13 | text: 'React router', 14 | done: true 15 | }, { 16 | text: 'Bootstrap webpack', 17 | done: true 18 | }, { 19 | text: 'Sass modules (sass-loader css-loader style-loader)', 20 | done: true 21 | }, { 22 | text: 'React transform', 23 | done: true 24 | }, { 25 | text: 'Redux logger', 26 | done: true 27 | }, { 28 | text: 'React document meta', 29 | done: true 30 | }, { 31 | text: 'Redux form', 32 | done: true 33 | }, { 34 | text: 'Karma', 35 | done: true 36 | }, { 37 | text: 'Mocha', 38 | done: true 39 | }, { 40 | text: 'Server-side rendering', 41 | done: false 42 | }] 43 | }; 44 | 45 | export function items(state = initialState, action) { 46 | switch (action.type) { 47 | case types.ADD_ITEM: 48 | return { 49 | ...state, 50 | items: [ 51 | ...state.items, { 52 | text: action.fields.name.value 53 | } 54 | ] 55 | }; 56 | 57 | case types.DELETE_ITEM: 58 | return { 59 | ...state, 60 | items: [ 61 | ...state.items.slice(0, action.index), 62 | ...state.items.slice(+action.index + 1) 63 | ] 64 | }; 65 | 66 | default: 67 | return state; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /init_db.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('babel-core/register'); 4 | 5 | var mongoose = require('mongoose'), 6 | env = require('./env'), 7 | models = require('./src/server/models').default; 8 | 9 | //data 10 | var phones = require('./init_data/phones'), 11 | computers = require('./init_data/computers'), 12 | printers = require('./init_data/printers'); 13 | 14 | mongoose.connection.once('open', function () { 15 | 16 | var Func = models.Func, 17 | Log = models.Log, 18 | Phones = models.Phones, 19 | Computers = models.Computers, 20 | Printers = models.Printers, 21 | Notes = models.Notes; 22 | 23 | //DATA FOR FILLING 24 | var funcs = [{ 25 | func: 'function() {return 0;}' 26 | }]; 27 | 28 | //remove models functions 29 | var removeFuncs = function() { 30 | Func.remove({}).then(removeLogs); 31 | }; 32 | var removeLogs = function() { 33 | Log.remove({}).then(removePhones); 34 | }; 35 | var removePhones = function() { 36 | Phones.remove({}).then(removeComputers); 37 | }; 38 | var removeComputers = function() { 39 | Computers.remove({}).then(removePrinters); 40 | }; 41 | var removePrinters = function() { 42 | Printers.remove({}).then(addPhones); 43 | }; 44 | 45 | //add data functions 46 | var addPhones = function() { 47 | Phones.collection.insert(phones, addComputers); 48 | }; 49 | var addComputers = function() { 50 | Computers.collection.insert(computers, addPrinters); 51 | }; 52 | var addPrinters = function() { 53 | Printers.collection.insert(printers, addInnerFunc); 54 | }; 55 | var addInnerFunc = function() { 56 | Func.collection.insert(funcs, function() { 57 | console.log('data updated, connection is closed'); 58 | mongoose.connection.close(); 59 | }); 60 | }; 61 | 62 | //function to start working with db 63 | var startInitDb = function() { 64 | removeFuncs(); 65 | }; 66 | startInitDb(); 67 | 68 | }); 69 | 70 | mongoose.connect(env.get('mongo:uri'), env.get('mongo:options')); 71 | -------------------------------------------------------------------------------- /src/client/reducers/__tests__/phones-reducer.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { phones } from '../phones'; 4 | 5 | describe('Phones reducer:', () => { 6 | 7 | const initialState = { 8 | phones: [] 9 | }; 10 | 11 | it('should return the initial state', () => { 12 | expect(phones(initialState, {})).to.deep.equal(initialState); 13 | }); 14 | 15 | it('should handle ADD_PHONE', () => { 16 | const expectedState = { 17 | phones: [{ 18 | name: 'iphone' 19 | }] 20 | }; 21 | 22 | const phoneToAdd = { 23 | name: 'iphone' 24 | }; 25 | 26 | expect(phones(initialState, { 27 | type: 'ADD_PHONE', 28 | phone: phoneToAdd, 29 | })).to.deep.equal(expectedState); 30 | }); 31 | 32 | it('should handle DELETE_PHONE', () => { 33 | const expectedState = { 34 | phones: [{ 35 | name: 'iphone' 36 | }] 37 | }; 38 | 39 | const stateWithPhones = { 40 | phones: [{ 41 | name: 'iphone' 42 | }, { 43 | name: 'samsung' 44 | }] 45 | }; 46 | 47 | expect(phones(stateWithPhones, { 48 | type: 'DELETE_PHONE', 49 | index: 1 50 | })).to.deep.equal(expectedState); 51 | }); 52 | 53 | it('should handle GET_ALL_PHONES', () => { 54 | const phonesToGet = [{ 55 | name: 'iphone' 56 | }, { 57 | name: 'samsung' 58 | }]; 59 | 60 | const expectedState = { 61 | phones: [{ 62 | name: 'iphone' 63 | }, { 64 | name: 'samsung' 65 | }] 66 | }; 67 | 68 | expect(phones(initialState, { 69 | type: 'GET_ALL_PHONES', 70 | phones: phonesToGet 71 | })).to.deep.equal(expectedState); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /src/server/schema/computerType.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLObjectType, 3 | GraphQLBoolean, 4 | GraphQLString, 5 | GraphQLFloat, 6 | GraphQLInt, 7 | } from 'graphql'; 8 | 9 | export const ComputerType = new GraphQLObjectType({ 10 | name: 'Computer', 11 | description: 'A computer', 12 | fields: () => ({ 13 | mark: { 14 | type: GraphQLString, 15 | description: 'The mark of computer', 16 | }, 17 | model: { 18 | type: GraphQLString, 19 | description: 'The model of computer', 20 | }, 21 | color: { 22 | type: GraphQLString, 23 | description: 'The color of computer' 24 | }, 25 | wifi: { 26 | type: GraphQLString, 27 | description: 'The type of wifi' 28 | }, 29 | isLaptop: { 30 | type: GraphQLBoolean, 31 | description: 'The computer is laptop' 32 | }, 33 | diagonal: { 34 | type: GraphQLFloat, 35 | description: 'The monitor diagonal' 36 | }, 37 | coresNumber: { 38 | type: GraphQLInt, 39 | description: 'The cpu core count' 40 | }, 41 | usb2: { 42 | type: GraphQLInt, 43 | description: 'The usb2 ports count' 44 | }, 45 | usb3: { 46 | type: GraphQLInt, 47 | description: 'The usb3 ports count' 48 | }, 49 | ram: { 50 | type: GraphQLInt, 51 | description: 'The ram capacity' 52 | }, 53 | memory: { 54 | type: GraphQLInt, 55 | description: 'The memory capacity' 56 | }, 57 | videocard: { 58 | type: GraphQLString, 59 | description: 'The type of video card in computer' 60 | }, 61 | videomemory: { 62 | type: GraphQLInt, 63 | description: 'The video memory capacity computer' 64 | }, 65 | processor: { 66 | type: GraphQLString, 67 | description: 'The type of processor in computer' 68 | }, 69 | operatingSystem: { 70 | type: GraphQLString, 71 | description: 'The OS on computer' 72 | }, 73 | price: { 74 | type: GraphQLFloat, 75 | description: 'The price of computer' 76 | }, 77 | }) 78 | }); 79 | -------------------------------------------------------------------------------- /src/client/components/Modals/AddPhoneModal.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { 5 | Button, 6 | Modal, 7 | Input 8 | } from 'react-bootstrap'; 9 | 10 | export default class BaseModal extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | this.showModal = false; 14 | } 15 | 16 | close = async () => { 17 | console.log('close modal add phone'); 18 | this.showModal = false; 19 | this.forceUpdate(); 20 | } 21 | 22 | open = () => { 23 | this.showModal = true; 24 | this.forceUpdate(); 25 | } 26 | 27 | render() { 28 | 29 | return ( 30 |
31 | 32 | 38 | 39 | 40 | 41 | Please add data about new phone 42 | 43 | 44 |

Please, type fill information

45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 | 60 | 61 | 62 |
63 |
64 | ); 65 | } 66 | } -------------------------------------------------------------------------------- /src/client/components/Tools/index.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { Image, ResponsiveEmbed } from 'react-bootstrap'; 5 | 6 | /* images */ 7 | const reactjs = require('./files/reactjs.png'); 8 | const redux = require('./files/redux.png'); 9 | const babel = require('./files/babel.png'); 10 | const webpack = require('./files/webpack.png'); 11 | const bootstrap = require('./files/bootstrap.png'); 12 | const mocha = require('./files/mocha.png'); 13 | 14 | export default class Tools extends Component { 15 | 16 | render() { 17 | return ( 18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 |
38 | 39 |
40 | 41 | 42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 |
52 | 53 | 54 | 55 |
56 |
57 |
58 |
59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/client/components/Header.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import { Navbar, Nav, NavItem, NavDropdown, MenuItem, Input, Button, PageHeader } from 'react-bootstrap'; 5 | import { Link } from 'react-router' 6 | 7 | export default class Header extends React.Component { 8 | render() { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 | Go to Main page 16 | 17 | 18 | 19 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | ); 56 | } 57 | } -------------------------------------------------------------------------------- /webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var Webpack = require('webpack'); 3 | var server = require('./utils/app-server'); 4 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 5 | 6 | var NODE_ENV = process.env.NODE_ENV; //variable of the scope 7 | 8 | module.exports = { 9 | entry: { 10 | shop: [ 11 | 'webpack-dev-server/client', 12 | 'webpack/hot/dev-server', 13 | path.join(__dirname, '../src/client/') 14 | ] 15 | }, 16 | 17 | output: { 18 | path: path.join(__dirname, '../src/client/build'), 19 | filename: '[name]-build.js', 20 | publicPath: '/' 21 | }, 22 | 23 | module: { 24 | loaders: [{ test: /\.html/, loader: 'html-loader' }, 25 | {loader: "babel-loader", 26 | test: /\.(jsx|es)$/, 27 | query: { 28 | plugins: ['transform-runtime', 'transform-decorators-legacy'], 29 | presets: ['es2015', 'stage-0', 'react'], 30 | } 31 | }, 32 | //{ test: /\.(jsx|es)$/, loader: 'babel-loader?presets[]=react,presets[]=es2015,presets[]=stage-0' }, 33 | { test: /\.json$/, loader: 'json-loader' }, 34 | { test: /\.json5$/, loader: 'json5-loader' }, 35 | { test: /\.(png|jpg|jpeg|gif)$/, loader: 'file-loader' }, 36 | { test: /\.(woff|woff2)$/, loader: 'url-loader?limit=100000' }, 37 | { test: /\.(ttf|eot|wav|mp3|svg|eot|woff|woff2)$/, loader: 'file?name=[name].[ext][hash]' }, 38 | { test: /\.(wav|mp3)$/, loader: 'file-loader' }, 39 | { test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'css-loader?minimize!less-loader') }, 40 | { test: /\.(sass|scss)$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?minimize!sass-loader') }, 41 | { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?minimize') } 42 | ] 43 | }, 44 | 45 | progress: true, 46 | devtool: NODE_ENV === 'development' ? "cheap-inline-module-source-map" : null, 47 | 48 | resolve: { 49 | modulesDirectories: ['node_modules', 'src/client', 'src/server'], 50 | extensions: ['', '.jsx', '.js', '.es', '.json', 'css', 'scss'] 51 | }, 52 | 53 | plugins: [ 54 | new ExtractTextPlugin('[name]-build.css'), 55 | new Webpack.HotModuleReplacementPlugin(), 56 | new Webpack.NoErrorsPlugin(), 57 | 58 | new Webpack.DefinePlugin({ 59 | 'process.env': { 60 | BROWSER: JSON.stringify(true), 61 | NODE_ENV: JSON.stringify('development') 62 | } 63 | }), 64 | 65 | function () { 66 | this.plugin("done", function(stats) { 67 | if (stats.compilation.errors && stats.compilation.errors.length && process.argv.indexOf('--watch') == -1) 68 | { 69 | console.log(stats.compilation.errors); 70 | } else { 71 | console.log('success build'); 72 | server(); 73 | } 74 | }); 75 | } 76 | ] 77 | }; 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shop", 3 | "version": "0.0.6", 4 | "description": "Shop", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "node ./webpack.js", 8 | "build": "./node_modules/.bin/webpack --config ./webpack/webpack.config.js", 9 | "add-db": "node ./init_db.js", 10 | "clean": "rm -r ./client/build", 11 | "test-client": "mocha --timeout 10000 --require=\"mocha_settings/client-inject.js\" --compilers js:babel-core/register \"src/client/**/__tests__/**/*.spec.js\"", 12 | "test-server": "mocha --timeout 10000 --require=\"mocha_settings/server-inject.js\" --compilers js:babel-core/register \"src/server/**/__tests__/**/*.spec.js\"", 13 | "test": "npm run test-client && npm run test-server" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/yankouskia/shop" 18 | }, 19 | "author": "yankouskia", 20 | "license": "ISC", 21 | "devDependencies": { 22 | "babel-core": "6.10.4", 23 | "babel-eslint": "3.0.1", 24 | "babel-loader": "6.2.3", 25 | "babel-plugin-transform-decorators-legacy": "1.3.4", 26 | "babel-plugin-transform-runtime": "6.5.2", 27 | "babel-polyfill": "6.5.0", 28 | "babel-preset-es2015": "6.5.0", 29 | "babel-preset-react": "6.5.0", 30 | "babel-preset-stage-0": "6.5.0", 31 | "babel-runtime": "6.5.0", 32 | "bluebird": "3.3.4", 33 | "chai": "3.5.0", 34 | "chai-subset": "1.2.1", 35 | "classnames": "2.2.3", 36 | "css-loader": "0.15.4", 37 | "ejs": "2.3.4", 38 | "eslint": "0.22.0", 39 | "eslint-plugin-react": "2.2.0", 40 | "express": "4.13.1", 41 | "extract-text-webpack-plugin": "0.8.2", 42 | "file-loader": "0.8.4", 43 | "history": "2.0.0-rc2", 44 | "html-loader": "0.4.3", 45 | "immutable": "3.7.4", 46 | "json-loader": "0.5.2", 47 | "json5": "0.4.0", 48 | "json5-loader": "0.6.0", 49 | "lodash": "4.6.1", 50 | "mocha": "2.4.5", 51 | "mongoose-timestamp": "0.5.0", 52 | "node-libs-browser": "0.5.2", 53 | "node-sass": "3.10.0", 54 | "node-watch": "0.3.5", 55 | "nskeymirror": "0.1.2", 56 | "opener": "1.4.1", 57 | "react": "0.14.7", 58 | "react-dnd": "2.0.2", 59 | "react-loading-order-with-animation": "1.0.0", 60 | "react-dnd-html5-backend": "2.1.2", 61 | "react-document-meta": "2.0.0-rc2", 62 | "react-dom": "0.14.7", 63 | "react-redux": "4.0.6", 64 | "redux": "3.1.4", 65 | "redux-form": "4.1.5", 66 | "redux-logger": "2.4.0", 67 | "react-router": "2.0.0-rc5", 68 | "redux-thunk": "0.1.0", 69 | "reselect": "2.2.1", 70 | "sass-loader": "1.0.2", 71 | "sinon-chai": "2.8.0", 72 | "style-loader": "0.12.3", 73 | "superagent": "1.8.1", 74 | "url-loader": "0.5.6", 75 | "webpack": "1.10.1" 76 | }, 77 | "dependencies": { 78 | "async": "1.5.0", 79 | "body-parser": "1.14.1", 80 | "bootstrap": "3.3.6", 81 | "bootstrap-sass": "3.3.6", 82 | "express-graphql": "^0.5.4", 83 | "graphql": "^0.7.2", 84 | "helmet": "0.14.0", 85 | "hpp": "0.2.0", 86 | "material-ui": "0.14.0", 87 | "mongoose": "4.4.8", 88 | "nconf": "0.8.2", 89 | "react-bootstrap": "0.28.3", 90 | "webpack-dev-server": "1.12.1" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/client/containers/PhonesContainer.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component, PropTypes } from 'react'; 4 | import { 5 | Accordion, 6 | Panel, 7 | Well, 8 | ListGroupItem, 9 | ListGroup 10 | } from 'react-bootstrap'; 11 | import PhonesAddModal from 'components/Modals/AddPhoneModal.react'; 12 | import { bindActionCreators } from 'redux'; 13 | import { connect } from 'react-redux'; 14 | 15 | import * as phonesActions from 'actions/phones'; 16 | 17 | function mapStateToProps(state) { 18 | return { 19 | phones: state.phones.phones, 20 | }; 21 | } 22 | 23 | function mapDispatchToProps(dispatch) { 24 | return bindActionCreators({ 25 | ...phonesActions, 26 | }, dispatch); 27 | } 28 | 29 | class PhonesTable extends Component { 30 | constructor(props) { 31 | super(props); 32 | this.props.getAllPhones(); 33 | } 34 | 35 | render() { 36 | let phones = this.props.phones; 37 | return ( 38 |
39 | Here is all list of our phones 40 |
41 | 42 | 43 | { 44 | phones.sort((left, right) => {return left.mark.toLowerCase() > right.mark.toLowerCase() ? 1 : -1}).map((phone, index) => { 45 | const header = `${phone.mark} ${phone.model}; System: ${phone.operatingSystem}; Color: ${phone.color}`; 46 | return 47 | 48 | {phone.ram} 49 | {phone.camera} 50 | {phone.color} 51 | {phone.coresNumber} 52 | {phone.gps} 53 | {phone.wifi} 54 | {phone.diagonal} 55 | {phone.operatingSystem} 56 | {phone.price} 57 | 58 | 59 | }) 60 | } 61 | 62 |
63 |
64 | ); 65 | } 66 | } 67 | 68 | PhonesTable.propTypes = { 69 | getAllPhones: PropTypes.func.isRequired, 70 | addPhone: PropTypes.func.isRequired, 71 | deletePhone: PropTypes.func.isRequired, 72 | phones: PropTypes.array.isRequired 73 | }; 74 | 75 | export default connect(mapStateToProps, mapDispatchToProps)(PhonesTable); -------------------------------------------------------------------------------- /src/client/containers/ComputersContainer.react.es: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component, PropTypes } from 'react'; 4 | import { 5 | Accordion, 6 | Panel, 7 | Well, 8 | ListGroupItem, 9 | ListGroup 10 | } from 'react-bootstrap'; 11 | import { bindActionCreators } from 'redux'; 12 | import { connect } from 'react-redux'; 13 | 14 | import * as computersActions from 'actions/computers'; 15 | 16 | function mapStateToProps(state) { 17 | return { 18 | computers: state.computers.computers, 19 | }; 20 | } 21 | 22 | function mapDispatchToProps(dispatch) { 23 | return bindActionCreators({ 24 | ...computersActions, 25 | }, dispatch); 26 | } 27 | 28 | class ComputersTable extends Component { 29 | constructor(props) { 30 | super(props); 31 | this.props.getAllComputers(); 32 | } 33 | 34 | render() { 35 | let computers = this.props.computers; 36 | return ( 37 |
38 | Here is all list of our computers 39 |
40 | 41 | { 42 | computers.sort((left, right) => {return left.mark.toLowerCase() > right.mark.toLowerCase() ? 1 : -1}).map((computer, index) => { 43 | const header = `${computer.mark} ${computer.model}; Operating System: ${computer.operatingSystem}; Memory(GB): ${computer.memory}`; 44 | return 45 | 46 | {computer.color} 47 | {computer.wifi} 48 | {computer.isLaptop ? 'Yes' : 'No'} 49 | {computer.diagonal} 50 | {computer.coresNumber} 51 | {computer.usb2} 52 | {computer.usb3} 53 | {computer.ram} 54 | {computer.memory} 55 | {computer.videocard} 56 | {computer.videomemory} 57 | {computer.operatingSystem} 58 | {computer.price} 59 | 60 | 61 | }) 62 | } 63 | 64 |
65 |
66 | ); 67 | } 68 | } 69 | 70 | ComputersTable.propTypes = { 71 | getAllComputers: PropTypes.func.isRequired, 72 | computers: PropTypes.array.isRequired 73 | }; 74 | 75 | export default connect(mapStateToProps, mapDispatchToProps)(ComputersTable); -------------------------------------------------------------------------------- /src/server/schema/schema.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLObjectType, 3 | GraphQLSchema, 4 | GraphQLList, 5 | GraphQLString, 6 | GraphQLBoolean, 7 | GraphQLInt, 8 | GraphQLFloat, 9 | GraphQLNonNull 10 | } from 'graphql'; 11 | 12 | import { ComputerType } from './computerType'; 13 | import { PhoneType } from './phoneType'; 14 | import { PrinterType } from './printerType'; 15 | 16 | import ComputerModel from '../models/computers'; 17 | import PhoneModel from '../models/phones'; 18 | import PrinterModel from '../models/printers'; 19 | 20 | import computersCtrl from '../controllers/computers' 21 | import phonesCtrl from '../controllers/phones'; 22 | import printersCtrl from '../controllers/printers'; 23 | 24 | 25 | let schema = new GraphQLSchema({ 26 | query: new GraphQLObjectType({ 27 | name: 'RootQueryType', 28 | fields: { 29 | computers: { 30 | type: new GraphQLList(ComputerType), 31 | args: { 32 | mark: { 33 | description: 'The mark of computer', 34 | type: GraphQLString 35 | } 36 | }, 37 | resolve: (root, { mark }) => computersCtrl.getAll(mark) 38 | }, 39 | phones: { 40 | type: new GraphQLList(PhoneType), 41 | args: { 42 | mark: { 43 | type: GraphQLString, 44 | description: 'The mark of phone for search.' 45 | } 46 | }, 47 | resolve: (root, { mark }) => phonesCtrl.getAll(mark) 48 | }, 49 | printers: { 50 | type: new GraphQLList(PrinterType), 51 | args: { 52 | mark: { 53 | type: GraphQLString, 54 | description: 'The mark of printer for search.' 55 | } 56 | }, 57 | resolve: (root, { mark }) => printersCtrl.getAll(mark) 58 | } 59 | } 60 | }), 61 | 62 | mutation: new GraphQLObjectType({ 63 | name: 'Mutation', 64 | fields: { 65 | insertComputer: { 66 | type: ComputerType, 67 | args: { 68 | mark: { 69 | type: new GraphQLNonNull(GraphQLString), 70 | description: 'The mark of computer', 71 | }, 72 | model: { 73 | type: new GraphQLNonNull(GraphQLString), 74 | description: 'The model of computer', 75 | }, 76 | color: { 77 | type: new GraphQLNonNull(GraphQLString), 78 | description: 'The color of computer' 79 | }, 80 | wifi: { 81 | type: new GraphQLNonNull(GraphQLString), 82 | description: 'The type of wifi' 83 | }, 84 | isLaptop: { 85 | type: new GraphQLNonNull(GraphQLBoolean), 86 | description: 'The computer is laptop' 87 | }, 88 | diagonal: { 89 | type: new GraphQLNonNull(GraphQLFloat), 90 | description: 'The monitor diagonal' 91 | }, 92 | coresNumber: { 93 | type: new GraphQLNonNull(GraphQLInt), 94 | description: 'The cpu core count' 95 | }, 96 | usb2: { 97 | type: new GraphQLNonNull(GraphQLInt), 98 | description: 'The usb2 ports count' 99 | }, 100 | usb3: { 101 | type: new GraphQLNonNull(GraphQLInt), 102 | description: 'The usb3 ports count' 103 | }, 104 | ram: { 105 | type: new GraphQLNonNull(GraphQLInt), 106 | description: 'The ram capacity' 107 | }, 108 | memory: { 109 | type: new GraphQLNonNull(GraphQLInt), 110 | description: 'The memory capacity' 111 | }, 112 | videocard: { 113 | type: new GraphQLNonNull(GraphQLString), 114 | description: 'The type of video card in computer' 115 | }, 116 | videomemory: { 117 | type: new GraphQLNonNull(GraphQLInt), 118 | description: 'The video memory capacity computer' 119 | }, 120 | processor: { 121 | type: new GraphQLNonNull(GraphQLString), 122 | description: 'The type of processor in computer' 123 | }, 124 | operatingSystem: { 125 | type: new GraphQLNonNull(GraphQLString), 126 | description: 'The OS on computer' 127 | }, 128 | price: { 129 | type: new GraphQLNonNull(GraphQLFloat), 130 | description: 'The price of computer' 131 | } 132 | }, 133 | resolve: (obj, computer ) => computersCtrl.add(computer) 134 | }, 135 | insertPhone: { 136 | type: PhoneType, 137 | args: { 138 | mark: { 139 | type: new GraphQLNonNull(GraphQLString), 140 | description: 'The mark of phone.' 141 | }, 142 | model: { 143 | type: new GraphQLNonNull(GraphQLString), 144 | description: 'The model of phone.' 145 | }, 146 | color: { 147 | type: new GraphQLNonNull(GraphQLString), 148 | description: 'The color of phone.' 149 | }, 150 | wifi: { 151 | type: new GraphQLNonNull(GraphQLString), 152 | description: 'The wifi of phone.' 153 | }, 154 | gps: { 155 | type: new GraphQLNonNull(GraphQLBoolean), 156 | description: 'The gps of phone.' 157 | }, 158 | coresNumber: { 159 | type: new GraphQLNonNull(GraphQLInt), 160 | description: 'The coresNumber of phone.' 161 | }, 162 | ram: { 163 | type: new GraphQLNonNull(GraphQLInt), 164 | description: 'The ram of phone.' 165 | }, 166 | memory: { 167 | type: new GraphQLNonNull(GraphQLInt), 168 | description: 'The memory of phone.' 169 | }, 170 | camera: { 171 | type: new GraphQLNonNull(GraphQLInt), 172 | description: 'The camera of phone.' 173 | }, 174 | diagonal: { 175 | type: new GraphQLNonNull(GraphQLFloat), 176 | description: 'The diagonal of phone.' 177 | }, 178 | operatingSystem: { 179 | type: new GraphQLNonNull(GraphQLString), 180 | description: 'The operatingSystem of phone.' 181 | }, 182 | price: { 183 | type: new GraphQLNonNull(GraphQLFloat), 184 | description: 'The price of phone.' 185 | } 186 | }, 187 | resolve: (obj, phone) => phonesCtrl.add(phone) 188 | }, 189 | insertPrinter: { 190 | type: PrinterType, 191 | args: { 192 | mark: { 193 | type: new GraphQLNonNull(GraphQLString), 194 | description: 'The mark of printer.' 195 | }, 196 | model: { 197 | type: new GraphQLNonNull(GraphQLString), 198 | description: 'The model of printer.' 199 | }, 200 | color: { 201 | type: new GraphQLNonNull(GraphQLString), 202 | description: 'The color of printer.' 203 | }, 204 | wifi: { 205 | type: new GraphQLNonNull(GraphQLString), 206 | description: 'The wifi of printer.' 207 | }, 208 | isMFP: { 209 | type: new GraphQLNonNull(GraphQLBoolean), 210 | description: 'The isMFP of printer.' 211 | }, 212 | isBlackWhite: { 213 | type: new GraphQLNonNull(GraphQLBoolean), 214 | description: 'The isBlackWhite of printer.' 215 | }, 216 | pageVelocityPerMinute: { 217 | type: new GraphQLNonNull(GraphQLFloat), 218 | description: 'The pageVelocityPerMinute of printer.' 219 | }, 220 | printTechnology: { 221 | type: new GraphQLNonNull(GraphQLString), 222 | description: 'The printTechnology of printer.' 223 | }, 224 | price: { 225 | type: new GraphQLNonNull(GraphQLFloat), 226 | description: 'The price of printer.' 227 | } 228 | }, 229 | resolve: (obj, printer) => printersCtrl.add(printer) 230 | } 231 | } 232 | }) 233 | }); 234 | 235 | export default schema; 236 | --------------------------------------------------------------------------------