├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── bin
├── _lib.sh
├── check_nulls
├── create_migration
├── fixtures
├── migrate
└── sync_tests
├── build
├── config.js
├── utilities.js
└── webpack
│ ├── base.js
│ ├── fixtures.js
│ ├── frontend.js
│ ├── karma.js
│ ├── migrate.js
│ └── webserver.js
├── gulpfile.babel.js
├── karma.conf.js
├── package.json
├── public
└── humans.txt
├── src
├── api
│ ├── index.js
│ └── usersPageApiRequest.js
├── commands
│ ├── fixtures.js
│ ├── migrate.js
│ └── webserver.js
├── components
│ ├── AboutPage
│ │ ├── AboutPage.js
│ │ └── index.js
│ ├── Application
│ │ ├── Application.js
│ │ └── index.js
│ ├── ErrorPage
│ │ ├── ErrorPage.js
│ │ └── index.js
│ ├── HomePage
│ │ ├── HomePage.js
│ │ └── index.js
│ ├── Link
│ │ ├── Link.js
│ │ └── index.js
│ ├── NotFoundPage
│ │ ├── NotFoundPage.js
│ │ └── index.js
│ ├── SignIn
│ │ ├── SignIn.js
│ │ └── index.js
│ ├── UsersPage
│ │ ├── Item.js
│ │ ├── Order.js
│ │ ├── Position.js
│ │ ├── User.js
│ │ ├── UsersPage.js
│ │ └── index.js
│ └── pages.js
├── config
│ ├── index.js
│ └── routing.js
├── fixtures
│ ├── 01-users.js
│ ├── 02-items.js
│ ├── 03-orders.js
│ └── 04-orderpositions.js
├── flux
│ ├── resources
│ │ ├── resourcesActions.js
│ │ ├── resourcesConstants.js
│ │ └── resourcesStore.js
│ ├── router
│ │ ├── routerActions.js
│ │ ├── routerConstants.js
│ │ └── routerStore.js
│ ├── signIn
│ │ ├── signInActions.js
│ │ ├── signInConstants.js
│ │ ├── signInStore.js
│ │ └── signInValidator.js
│ ├── stores.js
│ ├── token
│ │ ├── tokenActions.js
│ │ ├── tokenConstants.js
│ │ └── tokenStore.js
│ └── transition
│ │ ├── createFetchTransition.js
│ │ ├── createStaticTransition.js
│ │ └── transitionActions.js
├── frontend
│ ├── index.js
│ └── polyfills.js
├── migrations
│ ├── m20150624_180904_user.js
│ ├── m20150625_113856_item.js
│ ├── m20150625_113950_order.js
│ ├── m20150625_114024_userorder.js
│ ├── m20150625_114102_orderposition.js
│ ├── m20150625_161547_accesstoken.js
│ └── m20150625_161713_usertoken.js
├── schemas
│ ├── AccessToken.js
│ ├── Item.js
│ ├── Order.js
│ ├── OrderPosition.js
│ ├── User.js
│ └── index.js
├── utilities
│ ├── apiRequest.js
│ ├── bindAction.js
│ ├── collectResources.js
│ ├── createDispatcher.js
│ ├── createStore.js
│ ├── getCurrentUser.js
│ ├── patchReact.js
│ ├── preventEvent.js
│ └── progressIndicator.js
└── webserver
│ ├── db
│ ├── applyTransaction.js
│ ├── errors.js
│ ├── fulfillQuery.js
│ ├── mapObjectToResource.js
│ └── mapResourceToTransaction.js
│ ├── di
│ ├── app.js
│ ├── config.js
│ ├── cookieAuthMiddleware.js
│ ├── orientoDb.js
│ ├── orientoDbMiddleware.js
│ ├── orientoServer.js
│ ├── passport.js
│ └── wrapPassportMiddleware.js
│ ├── handlers
│ ├── index.js
│ ├── tokens.js
│ ├── users.js
│ └── xhr.js
│ ├── index.js
│ ├── polyfills.js
│ └── utilities
│ ├── appTemplate.js
│ ├── getParamsFromRequest.js
│ ├── renderPage.js
│ ├── validateResourceBySchema.js
│ └── wrapHandler.js
├── test
├── .eslintrc
├── api
│ ├── indexTest.js
│ └── usersPageApiRequestTest.js
├── components
│ ├── AboutPage
│ │ ├── AboutPageTest.js
│ │ └── indexTest.js
│ ├── Application
│ │ ├── ApplicationTest.js
│ │ └── indexTest.js
│ ├── ErrorPage
│ │ ├── ErrorPageTest.js
│ │ └── indexTest.js
│ ├── HomePage
│ │ ├── HomePageTest.js
│ │ └── indexTest.js
│ ├── Link
│ │ ├── LinkTest.js
│ │ └── indexTest.js
│ ├── NotFoundPage
│ │ ├── NotFoundPageTest.js
│ │ └── indexTest.js
│ ├── SignIn
│ │ ├── SignInTest.js
│ │ └── indexTest.js
│ ├── UsersPage
│ │ ├── ItemTest.js
│ │ ├── OrderTest.js
│ │ ├── PositionTest.js
│ │ ├── UserTest.js
│ │ ├── UsersPageTest.js
│ │ └── indexTest.js
│ └── pagesTest.js
├── config
│ └── routingTest.js
├── flux
│ ├── resources
│ │ ├── resourcesActionsTest.js
│ │ ├── resourcesConstantsTest.js
│ │ └── resourcesStoreTest.js
│ ├── router
│ │ ├── routerActionsTest.js
│ │ ├── routerConstantsTest.js
│ │ └── routerStoreTest.js
│ ├── signIn
│ │ ├── signInActionsTest.js
│ │ ├── signInConstantsTest.js
│ │ ├── signInStoreTest.js
│ │ └── signInValidatorTest.js
│ ├── storesTest.js
│ ├── token
│ │ ├── tokenActionsTest.js
│ │ ├── tokenConstantsTest.js
│ │ └── tokenStoreTest.js
│ └── transition
│ │ ├── createFetchTransitionTest.js
│ │ ├── createStaticTransitionTest.js
│ │ └── transitionActionsTest.js
├── schemas
│ ├── AccessTokenTest.js
│ ├── ItemTest.js
│ ├── OrderPositionTest.js
│ ├── OrderTest.js
│ ├── UserTest.js
│ └── indexTest.js
├── utilities
│ ├── apiRequestTest.js
│ ├── bindActionTest.js
│ ├── collectResourcesTest.js
│ ├── createDispatcherTest.js
│ ├── createStoreTest.js
│ ├── getCurrentUserTest.js
│ ├── patchReactTest.js
│ ├── preventEventTest.js
│ └── progressIndicatorTest.js
└── webserver
│ ├── db
│ ├── applyTransactionTest.js
│ ├── errorsTest.js
│ ├── fulfillQueryTest.js
│ ├── mapObjectToResourceTest.js
│ └── mapResourceToTransactionTest.js
│ ├── handlers
│ ├── indexTest.js
│ ├── tokensTest.js
│ ├── usersTest.js
│ └── xhrTest.js
│ └── utilities
│ ├── appTemplateTest.js
│ ├── getParamsFromRequestTest.js
│ ├── renderPageTest.js
│ ├── validateResourceBySchemaTest.js
│ └── wrapHandlerTest.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0,
3 | "optional": [
4 | "runtime"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": [
4 | "react",
5 | "babel"
6 | ],
7 | "ecmaFeatures": {
8 | "modules": true
9 | },
10 | "env": {
11 | "browser": true,
12 | "node": true,
13 | "es6": true
14 | },
15 | "rules": {
16 | "comma-dangle": [2, "never"],
17 | "no-cond-assign": [2, "always"],
18 | "no-console": 2,
19 | "no-constant-condition": 2,
20 | "no-control-regex": 2,
21 | "no-debugger": 2,
22 | "no-dupe-args": 2,
23 | "no-dupe-keys": 2,
24 | "no-duplicate-case": 2,
25 | "no-empty-character-class": 2,
26 | "no-empty": 2,
27 | "no-ex-assign": 2,
28 | "no-extra-boolean-cast": 0,
29 | "no-extra-parens": 0,
30 | "no-extra-semi": 2,
31 | "no-func-assign": 2,
32 | "no-inner-declarations": [2, "both"],
33 | "no-invalid-regexp": 2,
34 | "no-irregular-whitespace": 2,
35 | "no-negated-in-lhs": 2,
36 | "no-obj-calls": 2,
37 | "no-regex-spaces": 2,
38 | "no-reserved-keys": 0,
39 | "no-sparse-arrays": 2,
40 | "no-unreachable": 2,
41 | "use-isnan": 2,
42 | "valid-jsdoc": 0,
43 | "valid-typeof": 2,
44 |
45 | "accessor-pairs": [2, {"setWithoutGet": false}],
46 | "block-scoped-var": 0,
47 | "complexity": 0,
48 | "consistent-return": 0,
49 | "curly": [2, "all"],
50 | "default-case": 2,
51 | "dot-notation": 0,
52 | "dot-location": [2, "property"],
53 | "eqeqeq": 2,
54 | "guard-for-in": 2,
55 | "no-alert": 2,
56 | "no-caller": 2,
57 | "no-div-regex": 2,
58 | "no-else-return": 0,
59 | "no-empty-label": 2,
60 | "no-eq-null": 2,
61 | "no-eval": 2,
62 | "no-extend-native": 2,
63 | "no-extra-bind": 2,
64 | "no-fallthrough": 0,
65 | "no-floating-decimal": 2,
66 | "no-implied-eval": 2,
67 | "no-iterator": 2,
68 | "no-labels": 2,
69 | "no-lone-blocks": 0,
70 | "no-loop-func": 0,
71 | "no-multi-spaces": 2,
72 | "no-multi-str": 2,
73 | "no-native-reassign": 2,
74 | "no-new-func": 2,
75 | "no-new-wrappers": 2,
76 | "no-new": 2,
77 | "no-octal-escape": 2,
78 | "no-octal": 2,
79 | "no-param-reassign": 0,
80 | "no-process-env": 0,
81 | "no-proto": 2,
82 | "no-redeclare": 2,
83 | "no-return-assign": 2,
84 | "no-script-url": 2,
85 | "no-self-compare": 2,
86 | "no-sequences": 2,
87 | "no-throw-literal": 2,
88 | "no-unused-expressions": 2,
89 | "no-void": 2,
90 | "no-warning-comments": [1, {"terms": ["todo", "fixme", "xxx"], "location": "start"}],
91 | "no-with": 2,
92 | "radix": 0,
93 | "vars-on-top": 0,
94 | "wrap-iife": [2, "inside"],
95 | "yoda": [2, "never"],
96 |
97 | "strict": [2, "never"],
98 |
99 | "no-catch-shadow": 2,
100 | "no-delete-var": 2,
101 | "no-label-var": 2,
102 | "no-shadow-restricted-names": 2,
103 | "no-shadow": 0,
104 | "no-undef-init": 2,
105 | "no-undef": 2,
106 | "no-undefined": 0,
107 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
108 | "no-use-before-define": [2, "nofunc"],
109 |
110 | "handle-callback-err": [2, "error"],
111 | "no-mixed-requires": [2, false],
112 | "no-new-require": 2,
113 | "no-path-concat": 2,
114 | "no-process-exit": 2,
115 | "no-restricted-modules": 0,
116 | "no-sync": 0,
117 |
118 | "brace-style": [2, "1tbs"],
119 | "camelcase": 2,
120 | "comma-spacing": 2,
121 | "comma-style": [2, "last"],
122 | "computed-property-spacing": [2, "never"],
123 | "consistent-this": [2, "that"],
124 | "eol-last": 2,
125 | "func-names": 0,
126 | "func-style": 0,
127 | "indent": [2, 2],
128 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
129 | "lines-around-comment": 2,
130 | "linebreak-style": [2, "unix"],
131 | "max-nested-callbacks": [1, 3],
132 | "new-cap": 2,
133 | "new-parens": 2,
134 | "newline-after-var": 2,
135 | "no-array-constructor": 2,
136 | "no-continue": 0,
137 | "no-inline-comments": 0,
138 | "no-lonely-if": 2,
139 | "no-mixed-spaces-and-tabs": [2, false],
140 | "no-multiple-empty-lines": [2, {"max": 2}],
141 | "no-nested-ternary": 2,
142 | "no-new-object": 2,
143 | "no-spaced-func": 2,
144 | "no-ternary": 0,
145 | "no-trailing-spaces": 2,
146 | "no-underscore-dangle": 2,
147 | "no-unneeded-ternary": 2,
148 | "no-wrap-func": 2,
149 | "object-curly-spacing": [2, "never"],
150 | "one-var": [2, "never"],
151 | "operator-assignment": [2, "always"],
152 | "operator-linebreak": [2, "before"],
153 | "padded-blocks": 0,
154 | "quote-props": 0,
155 | "quotes": [2, "single"],
156 | "semi-spacing": [2, {"before": false, "after": true}],
157 | "semi": [2, "never"],
158 | "sort-vars": [2, {"ignoreCase": false}],
159 | "space-after-keywords": [2, "always"],
160 | "space-before-blocks": [2, "always"],
161 | "space-before-function-paren": [2, "never"],
162 | "space-in-brackets": [2, "never"],
163 | "space-in-parens": [2, "never"],
164 | "space-infix-ops": 2,
165 | "space-return-throw-case": 2,
166 | "space-unary-ops": [2, {"words": true, "nonwords": false}],
167 | "spaced-comment": [2, "always"],
168 | "wrap-regex": 0,
169 |
170 | "generator-star-spacing": 0,
171 | "no-var": 2,
172 | "object-shorthand": 0,
173 | "prefer-const": 2,
174 |
175 | "max-depth": [1, 3],
176 | "max-len": [1, 120, 2],
177 | "max-params": [1, 3],
178 | "max-statements": [1, 25],
179 | "no-bitwise": 2,
180 | "no-plusplus": 0,
181 |
182 | "react/display-name": 0,
183 | "react/jsx-boolean-value": [2, "never"],
184 | "react/jsx-no-undef": 2,
185 | "react/jsx-quotes": [2, "double"],
186 | "react/jsx-sort-prop-types": 0,
187 | "react/jsx-sort-props": 0,
188 | "react/jsx-uses-react": 2,
189 | "react/jsx-uses-vars": 2,
190 | "react/no-did-mount-set-state": 2,
191 | "react/no-did-update-set-state": 2,
192 | "react/no-multi-comp": 0,
193 | "react/no-unknown-property": 2,
194 | "react/prop-types": 0,
195 | "react/react-in-jsx-scope": 0,
196 | "react/self-closing-comp": 2,
197 | "react/sort-comp": 2,
198 | "react/wrap-multilines": 2,
199 |
200 | "babel/object-shorthand": 2,
201 | "babel/generator-star-spacing": [2, "before"]
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.app/
2 | /.test/
3 | /node_modules/
4 |
5 | /env.sh
6 | /npm-debug.log
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Vyacheslav Slinko
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED
2 |
3 | Look for [ripster](https://github.com/vslinko/ripster) instead.
4 |
--------------------------------------------------------------------------------
/bin/_lib.sh:
--------------------------------------------------------------------------------
1 | create_env_sh() {
2 | cat << END > env.sh
3 | # Define your environment variables here
4 |
5 | \$*
6 | END
7 |
8 | chmod a+x env.sh
9 | }
10 |
11 | ensure_env_sh() {
12 | if [ ! -x env.sh ]; then
13 | create_env_sh
14 | fi
15 | }
16 |
--------------------------------------------------------------------------------
/bin/check_nulls:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd "$(dirname $0)/.."
4 |
5 | grep null -R build src test webpack.config.js gulpfile.babel.js | grep -v null-reasonable
6 |
7 | if [ $? == 0 ]; then
8 | exit 1
9 | else
10 | exit 0
11 | fi
12 |
--------------------------------------------------------------------------------
/bin/create_migration:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | name="$1"
4 |
5 | if [ "x$name" == "x" ]; then
6 | echo "Usage: $0 MIGRATION_NAME"
7 | exit 1
8 | fi
9 |
10 | cd "$(dirname $0)/.."
11 |
12 | mkdir -p src/migrations
13 |
14 | file_name="m$(date "+%Y%m%d")_$(date "+%H%M%S")_${name}.js"
15 |
16 | echo $file_name
17 |
18 | cat << END > "src/migrations/$file_name"
19 | export async function up(db) {
20 |
21 | }
22 |
23 | export async function down(db) {
24 |
25 | }
26 | END
27 |
--------------------------------------------------------------------------------
/bin/fixtures:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd "$(dirname $0)/.."
4 |
5 | . ./bin/_lib.sh
6 |
7 | ensure_env_sh
8 |
9 | ./node_modules/.bin/gulp _build-compile-fixtures >/dev/null
10 | ./env.sh node ./.app/fixtures.js
11 |
--------------------------------------------------------------------------------
/bin/migrate:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd "$(dirname $0)/.."
4 |
5 | . ./bin/_lib.sh
6 |
7 | ensure_env_sh
8 |
9 | ./node_modules/.bin/gulp _build-compile-migrate >/dev/null
10 | ./env.sh node ./.app/migrate.js
11 |
--------------------------------------------------------------------------------
/bin/sync_tests:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd "$(dirname $0)/.."
4 |
5 | files=$(find src -regex '.*.js')
6 |
7 | for file in $files; do
8 | if [[ $file == "src/webserver/di/"* ]]; then
9 | continue
10 | fi
11 | if [[ $file == "src/webserver/index.js" ]]; then
12 | continue
13 | fi
14 | if [[ $file == "src/frontend/index.js" ]]; then
15 | continue
16 | fi
17 | if [[ $file == *"/polyfills.js" ]]; then
18 | continue
19 | fi
20 | if [[ $file == "src/migrations/"* ]]; then
21 | continue
22 | fi
23 | if [[ $file == "src/fixtures/"* ]]; then
24 | continue
25 | fi
26 | if [[ $file == "src/commands/"* ]]; then
27 | continue
28 | fi
29 |
30 | test_file=${file/src/test}
31 | test_file=${test_file/.js/Test.js}
32 |
33 | test_dir=$(dirname $test_file)
34 |
35 | var_name=$(basename $file)
36 | var_name=${var_name/.js/}
37 |
38 | test_name=${file/src\//}
39 | test_name=${test_name/.js/}
40 |
41 | levels=$(echo $test_dir | tr -cd / | wc -c)
42 | path_to_file=""
43 |
44 | while (( $levels >= 0 )); do
45 | path_to_file="$path_to_file../"
46 | ((levels--))
47 | done
48 |
49 | path_to_file="$path_to_file$file"
50 | path_to_file=${path_to_file/.js/}
51 |
52 | mkdir -p $test_dir
53 |
54 | if [ -r $test_file ]; then
55 | continue
56 | fi
57 |
58 | cat << END > $test_file
59 | import $var_name from '$path_to_file' // eslint-disable-line
60 |
61 | describe('$test_name', () => {
62 | it('should work')
63 | })
64 | END
65 |
66 | echo "Created $test_file"
67 | done
68 |
--------------------------------------------------------------------------------
/build/config.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import fs from 'fs'
3 |
4 | const rootDirectory = path.join(__dirname, '..')
5 | const sourceDirectory = path.join(rootDirectory, 'src')
6 | const publicDirectory = path.join(rootDirectory, 'public')
7 | const testDirectory = path.join(rootDirectory, 'test')
8 | const destinationDirectory = path.join(rootDirectory, '.app')
9 | const destinationPublicDirectory = path.join(destinationDirectory, 'public')
10 |
11 | const production = process.env.NODE_ENV === 'production'
12 | const development = !production
13 | const hotReload = development && process.env.HOT_RELOAD === 'react-hot-loader'
14 |
15 | const nodeModulesExternals = fs.readdirSync(path.join(rootDirectory, 'node_modules'))
16 | .filter(dir => dir !== '.bin')
17 | .reduce((acc, name) => (acc[name] = `commonjs ${name}`, acc), {})
18 |
19 | {
20 | const envScript = path.join(rootDirectory, 'env.sh')
21 |
22 | if (fs.existsSync(envScript)) {
23 | fs.readFileSync(envScript)
24 | .toString()
25 | .split('\n')
26 | .filter(line => /^export /.test(line))
27 | .map(line => line.split('='))
28 | .map(parts => [parts[0].split(' ').pop(), parts[1]])
29 | .forEach(([key, value]) => process.env[key] = value)
30 | }
31 | }
32 |
33 | export default {
34 | rootDirectory,
35 | sourceDirectory,
36 | publicDirectory,
37 | testDirectory,
38 | destinationDirectory,
39 | destinationPublicDirectory,
40 |
41 | production,
42 | development,
43 | hotReload,
44 |
45 | nodeModulesExternals
46 | }
47 |
--------------------------------------------------------------------------------
/build/utilities.js:
--------------------------------------------------------------------------------
1 | import childProcess from 'child_process'
2 |
3 | export const useIf = (condition, value) =>
4 | condition ? value : undefined
5 |
6 | export const prepareArray = (array) =>
7 | array.filter(item => item !== undefined)
8 |
9 | export const mergeArrays = (...arrays) =>
10 | arrays.reduce((left, right) => [...left, ...right])
11 |
12 | export const mergeObjects = (...objects) =>
13 | Object.assign({}, ...objects)
14 |
15 | export const webpackCallback = ({onReady, onChange}) => {
16 | let first = true
17 |
18 | return (error, stats) => {
19 | if (error) {
20 | console.log(error) // eslint-disable-line no-console
21 | } else {
22 | console.log(stats.toString()) // eslint-disable-line no-console
23 | }
24 |
25 | if (first) {
26 | first = false
27 | if (onReady) {
28 | onReady()
29 | }
30 | } else if (onChange) {
31 | onChange()
32 | }
33 | }
34 | }
35 |
36 | export const runCommand = (command, args, callback) => {
37 | const options = {
38 | stdio: 'inherit'
39 | }
40 |
41 | childProcess.spawn(command, args, options)
42 | .on('close', code =>
43 | code > 0
44 | ? callback(new Error(command + ' exited with code ' + code))
45 | : callback()
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/build/webpack/base.js:
--------------------------------------------------------------------------------
1 | import {DefinePlugin, ProvidePlugin, optimize} from 'webpack'
2 |
3 | import {useIf, prepareArray} from '../utilities'
4 | import config from '../config'
5 |
6 | export default {
7 | module: {
8 | loaders: [
9 | {
10 | test: /\.js$/,
11 | include: [config.sourceDirectory],
12 | loaders: ['babel']
13 | }
14 | ]
15 | },
16 |
17 | debug: config.development,
18 |
19 | devtool: 'source-map',
20 |
21 | plugins: prepareArray([
22 | new DefinePlugin({
23 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
24 | }),
25 |
26 | new ProvidePlugin({
27 | 'React': 'react'
28 | }),
29 |
30 | useIf(config.production, new optimize.UglifyJsPlugin())
31 | ])
32 | }
33 |
--------------------------------------------------------------------------------
/build/webpack/fixtures.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | import {mergeObjects} from '../utilities'
4 | import config from '../config'
5 | import base from './base'
6 |
7 | export default mergeObjects(base, {
8 | entry: path.join(config.sourceDirectory, 'commands', 'fixtures'),
9 |
10 | output: {
11 | path: path.join(config.destinationDirectory),
12 | filename: 'fixtures.js'
13 | },
14 |
15 | target: 'node',
16 | node: {
17 | console: false,
18 | process: false,
19 | global: false,
20 | Buffer: false,
21 | __filename: false,
22 | __dirname: false
23 | },
24 |
25 | externals: [
26 | config.nodeModulesExternals
27 | ]
28 | })
29 |
--------------------------------------------------------------------------------
/build/webpack/frontend.js:
--------------------------------------------------------------------------------
1 | import {DefinePlugin, HotModuleReplacementPlugin} from 'webpack'
2 | import path from 'path'
3 |
4 | import {useIf, prepareArray, mergeArrays, mergeObjects} from '../utilities'
5 | import config from '../config'
6 | import base from './base'
7 |
8 | export default mergeObjects(base, {
9 | entry: prepareArray([
10 | useIf(config.hotReload, `webpack-dev-server/client?`),
11 | useIf(config.hotReload, 'webpack/hot/dev-server'),
12 | path.join(config.sourceDirectory, 'frontend')
13 | ]),
14 |
15 | output: {
16 | path: config.destinationPublicDirectory,
17 | filename: 'frontend.js',
18 | publicPath: '/',
19 | library: 'frontend'
20 | },
21 |
22 | module: mergeObjects(base.module, {
23 | loaders: mergeArrays(
24 | [
25 | {
26 | test: /\.js$/,
27 | include: [config.sourceDirectory],
28 | loaders: ['react-hot-loader']
29 | },
30 | {
31 | test: /\.css$/,
32 | loaders: ['style', 'css']
33 | }
34 | ],
35 | base.module.loaders
36 | )
37 | }),
38 |
39 | plugins: mergeArrays(
40 | prepareArray([
41 | new DefinePlugin({
42 | 'process.env.BACKEND_ADDRESS': JSON.stringify(process.env.BACKEND_ADDRESS || '')
43 | }),
44 | useIf(config.hotReload, new HotModuleReplacementPlugin())
45 | ]),
46 | base.plugins
47 | ),
48 |
49 | devServer: {
50 | contentBase: config.destinationPublicDirectory,
51 | filename: 'frontend.js',
52 | publicPath: '/',
53 | hot: config.hotReload,
54 | proxy: {
55 | '*': `http://localhost:${process.env.PORT}`
56 | }
57 | }
58 | })
59 |
--------------------------------------------------------------------------------
/build/webpack/karma.js:
--------------------------------------------------------------------------------
1 | import {mergeObjects, mergeArrays} from '../utilities'
2 | import config from '../config'
3 | import base from './base'
4 |
5 | export default mergeObjects(base, {
6 | module: mergeObjects(base.module, {
7 | loaders: mergeArrays(
8 | base.module.loaders,
9 | [
10 | {
11 | test: /\.js$/,
12 | include: [config.testDirectory],
13 | loaders: ['babel']
14 | }
15 | ]
16 | ).map(loaderConfig => {
17 | if (loaderConfig.include && loaderConfig.include.indexOf(config.sourceDirectory) >= 0) {
18 | loaderConfig = mergeObjects(loaderConfig, {
19 | loaders: ['isparta']
20 | })
21 | }
22 |
23 | return loaderConfig
24 | })
25 | }),
26 |
27 | devtool: undefined
28 | })
29 |
--------------------------------------------------------------------------------
/build/webpack/migrate.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | import {mergeObjects} from '../utilities'
4 | import config from '../config'
5 | import base from './base'
6 |
7 | export default mergeObjects(base, {
8 | entry: path.join(config.sourceDirectory, 'commands', 'migrate'),
9 |
10 | output: {
11 | path: path.join(config.destinationDirectory),
12 | filename: 'migrate.js'
13 | },
14 |
15 | target: 'node',
16 | node: {
17 | console: false,
18 | process: false,
19 | global: false,
20 | Buffer: false,
21 | __filename: false,
22 | __dirname: false
23 | },
24 |
25 | externals: [
26 | config.nodeModulesExternals,
27 | {
28 | 'oriento/lib/migration': 'commonjs oriento/lib/migration',
29 | 'oriento/lib/migration/manager': 'commonjs oriento/lib/migration/manager'
30 | }
31 | ]
32 | })
33 |
--------------------------------------------------------------------------------
/build/webpack/webserver.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | import {mergeObjects} from '../utilities'
4 | import config from '../config'
5 | import base from './base'
6 |
7 | export default mergeObjects(base, {
8 | entry: path.join(config.sourceDirectory, 'commands', 'webserver'),
9 |
10 | output: {
11 | path: path.join(config.destinationDirectory),
12 | filename: 'webserver.js'
13 | },
14 |
15 | target: 'node',
16 | node: {
17 | console: false,
18 | process: false,
19 | global: false,
20 | Buffer: false,
21 | __filename: false,
22 | __dirname: false
23 | },
24 |
25 | externals: [config.nodeModulesExternals]
26 | })
27 |
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp'
2 | import webpack from 'webpack'
3 | import WebpackDevServer from 'webpack-dev-server'
4 | import nodemon from 'nodemon'
5 | import path from 'path'
6 | import fs from 'fs-promise'
7 |
8 | import loadGulpPlugins from 'gulp-load-plugins'
9 | import runSequence from 'run-sequence'
10 | import del from 'del'
11 |
12 | import {webpackCallback, runCommand} from './build/utilities'
13 | import config from './build/config'
14 |
15 | const webpackFrontendConfig = () => require('./build/webpack/frontend')
16 | const webpackWebserverConfig = () => require('./build/webpack/webserver')
17 | const webpackMigrateConfig = () => require('./build/webpack/migrate')
18 | const webpackFixturesConfig = () => require('./build/webpack/fixtures')
19 | const plugins = loadGulpPlugins()
20 |
21 | /* BUILD */
22 |
23 | gulp.task('_build-clean', callback => {
24 | del(config.destinationDirectory, callback)
25 | })
26 |
27 | gulp.task('_build-copy-package-json', () =>
28 | gulp.src('package.json')
29 | .pipe(gulp.dest(config.destinationDirectory))
30 | )
31 |
32 | gulp.task('_build-copy-public-files', () =>
33 | gulp.src(`${config.publicDirectory}/**/*`)
34 | .pipe(gulp.dest(config.destinationPublicDirectory))
35 | )
36 |
37 | gulp.task('_build-compile-frontend', callback => {
38 | webpack(webpackFrontendConfig())
39 | .run(webpackCallback({onReady: callback}))
40 | })
41 |
42 | gulp.task('_build-compile-webserver', callback => {
43 | webpack(webpackWebserverConfig())
44 | .run(webpackCallback({onReady: callback}))
45 | })
46 |
47 | gulp.task('_build-compile-migrate', callback => {
48 | webpack(webpackMigrateConfig())
49 | .run(webpackCallback({onReady: callback}))
50 | })
51 |
52 | gulp.task('_build-compile-fixtures', callback => {
53 | webpack(webpackFixturesConfig())
54 | .run(webpackCallback({onReady: callback}))
55 | })
56 |
57 | gulp.task('build', callback => {
58 | runSequence(
59 | '_build-clean',
60 | [
61 | '_build-copy-package-json',
62 | '_build-copy-public-files',
63 | '_build-compile-frontend',
64 | '_build-compile-webserver',
65 | '_build-compile-migrate',
66 | '_build-compile-fixtures'
67 | ],
68 | callback
69 | )
70 | })
71 |
72 | /* START */
73 |
74 | gulp.task('_start-run-webserver', callback => {
75 | let first = true
76 |
77 | nodemon({
78 | script: path.join(config.destinationDirectory, 'webserver'),
79 | execMap: {
80 | '': 'node'
81 | },
82 | env: {
83 | PUBLIC_DIR: config.destinationPublicDirectory
84 | }
85 | }).on('start', function() {
86 | if (first) {
87 | first = false
88 | callback()
89 | }
90 | })
91 | })
92 |
93 | gulp.task('start', callback =>
94 | runSequence(
95 | 'build',
96 | '_start-run-webserver',
97 | callback
98 | )
99 | )
100 |
101 | /* WATCH */
102 |
103 | gulp.task('_watch-copy-public-files', ['_build-copy-public-files'], () =>
104 | gulp.watch(`${config.publicDirectory}/**/*`, ['_build-copy-public-files'])
105 | )
106 |
107 | gulp.task('_watch-compile-webserver', callback =>
108 | webpack(webpackWebserverConfig())
109 | .watch(100, webpackCallback({
110 | onReady: callback,
111 | onChange: () => {
112 | nodemon.restart()
113 | }
114 | }))
115 | )
116 |
117 | gulp.task('_watch-compile-migrate', callback =>
118 | webpack(webpackMigrateConfig())
119 | .watch(100, webpackCallback({
120 | onReady: callback
121 | }))
122 | )
123 |
124 | gulp.task('_watch-compile-fixtures', callback =>
125 | webpack(webpackFixturesConfig())
126 | .watch(100, webpackCallback({
127 | onReady: callback
128 | }))
129 | )
130 |
131 | gulp.task('_watch-webpack-dev-server', callback => {
132 | const config = webpackFrontendConfig()
133 |
134 | const server = new WebpackDevServer(
135 | webpack(config),
136 | config.devServer
137 | )
138 |
139 | server.listen(3000, '0.0.0.0', callback)
140 | })
141 |
142 | gulp.task('watch-webserver', callback =>
143 | runSequence(
144 | '_build-clean',
145 | [
146 | '_watch-copy-public-files',
147 | '_build-compile-frontend',
148 | '_watch-compile-webserver'
149 | ],
150 | '_start-run-webserver',
151 | callback
152 | )
153 | )
154 |
155 | gulp.task('watch-frontend', callback => {
156 | process.env.PORT = 3001
157 | process.env.HOT_RELOAD = 'react-hot-loader'
158 | config.hotReload = true
159 |
160 | runSequence(
161 | '_build-clean',
162 | [
163 | '_watch-copy-public-files',
164 | '_build-compile-webserver'
165 | ],
166 | '_start-run-webserver',
167 | '_watch-webpack-dev-server',
168 | callback
169 | )
170 | })
171 |
172 | gulp.task('watch-migrate', callback =>
173 | runSequence(
174 | '_build-clean',
175 | '_watch-compile-migrate',
176 | callback
177 | )
178 | )
179 |
180 | gulp.task('watch-fixtures', callback =>
181 | runSequence(
182 | '_build-clean',
183 | '_watch-compile-fixtures',
184 | callback
185 | )
186 | )
187 |
188 | gulp.task('watch-karma', callback =>
189 | runCommand(
190 | './node_modules/.bin/karma',
191 | ['start', '--log-level', 'debug', '--no-single-run'],
192 | callback
193 | )
194 | )
195 |
196 | /* ESLINT */
197 |
198 | gulp.task('eslint', () =>
199 | gulp.src([
200 | 'build/**/*.js',
201 | 'src/**/*.js',
202 | 'test/**/*.js',
203 | 'gulpfile.babel.js',
204 | 'webpack.config.js'
205 | ])
206 | .pipe(plugins.eslint())
207 | .pipe(plugins.eslint.format())
208 | .pipe(plugins.eslint.failAfterError())
209 | )
210 |
211 | /* KARMA */
212 |
213 | gulp.task('_karma-clean', callback =>
214 | del('.test', callback)
215 | )
216 |
217 | gulp.task('_karma-run', callback =>
218 | runCommand(
219 | './node_modules/.bin/karma',
220 | ['start', '--log-level', 'debug'],
221 | callback
222 | )
223 | )
224 |
225 | gulp.task('_karma-hide', async () => {
226 | await fs.mkdir('.test')
227 | await* [
228 | fs.rename('coverage', '.test/coverage'),
229 | fs.rename('test-results.xml', '.test/test-results.xml')
230 | ]
231 | })
232 |
233 | gulp.task('karma', callback =>
234 | runSequence(
235 | '_karma-clean',
236 | '_karma-run',
237 | '_karma-hide',
238 | callback
239 | )
240 | )
241 |
242 | /* GLOBAL */
243 |
244 | gulp.task('lint', ['eslint'])
245 | gulp.task('test', ['karma'])
246 | gulp.task('clean', ['_build-clean', '_karma-clean'])
247 | gulp.task('default', ['start'])
248 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-var */
2 |
3 | require('babel-core/register')
4 |
5 | var webpack = require('webpack')
6 | var webpackKarmaConfig = require('./build/webpack/karma')
7 |
8 | module.exports = function(config) {
9 | config.set({
10 | frameworks: ['mocha', 'sinon-chai'],
11 |
12 | files: [
13 | 'test/**/*Test.js'
14 | ],
15 |
16 | preprocessors: {
17 | 'test/**/*Test.js': ['webpack']
18 | },
19 |
20 | reporters: ['progress', 'junit', 'coverage'],
21 |
22 | browsers: ['Chrome'],
23 |
24 | singleRun: true,
25 |
26 | coverageReporter: {
27 | type: 'lcov',
28 | subdir: '.'
29 | },
30 |
31 | webpack: webpackKarmaConfig,
32 |
33 | webpackMiddleware: {
34 | quiet: true
35 | }
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "esex",
3 | "version": "1.0.0",
4 | "description": "Full-stack Universal JavaScript Framework",
5 | "keywords": [
6 | "full-stack",
7 | "universal",
8 | "isomorphic",
9 | "framework"
10 | ],
11 | "homepage": "https://github.com/esex/esex",
12 | "bugs": {
13 | "url": "https://github.com/esex/esex/issues",
14 | "email": "vslinko@yahoo.com"
15 | },
16 | "license": "MIT",
17 | "author": {
18 | "name": "Vyacheslav Slinko",
19 | "email": "vslinko@yahoo.com",
20 | "url": "https://twitter.com/vslinko"
21 | },
22 | "contributors": [],
23 | "main": "dist",
24 | "bin": {},
25 | "repository": {
26 | "type": "git",
27 | "url": "esex/esex"
28 | },
29 | "scripts": {
30 | "build": "gulp build",
31 | "check-nulls": "./bin/check_nulls",
32 | "clean": "gulp clean",
33 | "eslint": "gulp eslint",
34 | "karma": "gulp karma",
35 | "lint": "gulp lint",
36 | "start": "gulp start",
37 | "sync-tests": "./bin/sync_tests",
38 | "test": "gulp test",
39 | "watch-fixtures": "gulp watch-fixtures",
40 | "watch-frontend": "gulp watch-frontend",
41 | "watch-karma": "gulp watch-karma",
42 | "watch-migrate": "gulp watch-migrate",
43 | "watch-webserver": "gulp watch-webserver"
44 | },
45 | "config": {},
46 | "dependencies": {
47 | "access-rule": "1.0.0",
48 | "bcryptjs": "2.1.0",
49 | "body-parser": "1.13.1",
50 | "components-data-tree": "1.0.0",
51 | "cookie-parser": "1.3.5",
52 | "express": "4.13.0",
53 | "html5-history-api": "4.2.1",
54 | "lodash": "3.9.3",
55 | "match-route-pattern": "1.2.4",
56 | "morgan": "1.6.0",
57 | "node-fetch": "1.3.0",
58 | "nprogress": "0.2.0",
59 | "oriento": "1.2.0",
60 | "passport": "0.2.2",
61 | "passport-http": "0.2.2",
62 | "passport-http-bearer": "1.0.1",
63 | "ramda": "0.15.1",
64 | "react": "0.13.3",
65 | "react-observable": "1.1.3",
66 | "redux": "0.12.0",
67 | "rx": "2.5.3",
68 | "source-map-support": "0.3.1",
69 | "strulidator": "1.2.0",
70 | "whatwg-fetch": "0.9.0"
71 | },
72 | "devDependencies": {
73 | "babel": "5.6.5",
74 | "babel-core": "5.6.5",
75 | "babel-eslint": "3.1.18",
76 | "babel-loader": "5.1.4",
77 | "babel-runtime": "5.6.5",
78 | "css-loader": "0.15.1",
79 | "del": "1.2.0",
80 | "eslint": "0.23.0",
81 | "eslint-plugin-babel": "1.0.0",
82 | "eslint-plugin-react": "2.5.2",
83 | "fs-promise": "0.3.1",
84 | "gulp": "3.9.0",
85 | "gulp-eslint": "0.14.0",
86 | "gulp-load-plugins": "1.0.0-rc.1",
87 | "isparta-loader": "0.2.0",
88 | "karma": "0.12.37",
89 | "karma-chrome-launcher": "0.2.0",
90 | "karma-coverage": "0.4.2",
91 | "karma-junit-reporter": "0.2.2",
92 | "karma-mocha": "0.2.0",
93 | "karma-sinon-chai": "1.0.0",
94 | "karma-webpack": "1.5.1",
95 | "mocha": "2.2.5",
96 | "node-libs-browser": "0.5.2",
97 | "nodemon": "1.3.7",
98 | "react-hot-loader": "1.2.7",
99 | "run-sequence": "1.1.1",
100 | "style-loader": "0.12.3",
101 | "webpack": "1.9.11",
102 | "webpack-dev-server": "1.9.0"
103 | },
104 | "peerDependencies": {},
105 | "bundledDependencies": [],
106 | "optionalDependencies": {},
107 | "engines": {},
108 | "os": [],
109 | "cpu": [],
110 | "preferGlobal": false,
111 | "private": true,
112 | "publishConfig": {}
113 | }
114 |
--------------------------------------------------------------------------------
/public/humans.txt:
--------------------------------------------------------------------------------
1 | /* TEAM */
2 | Founder and Core Developer: Vyacheslav Slinko
3 | Contact: vslinko@yahoo.com
4 | Twitter: @vslinko
5 | From: Moscow, Russian Federation
6 |
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
1 | export {default as usersPageApiRequest} from './usersPageApiRequest'
2 |
--------------------------------------------------------------------------------
/src/api/usersPageApiRequest.js:
--------------------------------------------------------------------------------
1 | import apiRequest from '../utilities/apiRequest'
2 |
3 | export default async function usersPageApiRequest(queryParams, token) {
4 | const data = await apiRequest(`/api/v1/users`, {
5 | token,
6 | include: [
7 | 'orders',
8 | 'orders.positions',
9 | 'orders.positions.item'
10 | ]
11 | })
12 |
13 | return data
14 | }
15 |
--------------------------------------------------------------------------------
/src/commands/fixtures.js:
--------------------------------------------------------------------------------
1 | import 'source-map-support/register'
2 |
3 | import orientoServer from '../webserver/di/orientoServer'
4 | import orientoDb from '../webserver/di/orientoDb'
5 |
6 | const fixtures = require.context('../fixtures', false, /\.js$/)
7 |
8 | async function cleanDatabase() {
9 | const classNames = (await orientoDb.class.list())
10 | .map(cls => cls.name)
11 | .filter(name =>
12 | name !== 'V' && name !== 'E'
13 | && !/^O[A-Z]/.test(name) && !/^_/.test(name)
14 | && name !== 'Migration'
15 | )
16 |
17 | await* classNames
18 | .map(name => orientoDb.exec(`TRUNCATE CLASS ${name}`))
19 | }
20 |
21 | async function applyFixtures() {
22 | const files = fixtures.keys()
23 | let stash = {}
24 |
25 | for (const file of files) {
26 | const fixture = fixtures(file)
27 | const stashChanges = (await fixture(orientoDb, stash)) || {}
28 |
29 | stash = {...stash, ...stashChanges}
30 | }
31 | }
32 |
33 | async function main() {
34 | try {
35 | await cleanDatabase()
36 | await applyFixtures()
37 | } catch (error) {
38 | console.log(error.stack) // eslint-disable-line no-console
39 | } finally {
40 | orientoServer.close()
41 | }
42 | }
43 |
44 | main()
45 |
--------------------------------------------------------------------------------
/src/commands/migrate.js:
--------------------------------------------------------------------------------
1 | import 'source-map-support/register'
2 |
3 | import Migration from 'oriento/lib/migration'
4 | import MigrationManager from 'oriento/lib/migration/manager'
5 |
6 | import orientoDb from '../webserver/di/orientoDb'
7 |
8 | const migrations = require.context('../migrations', false, /\.js$/)
9 |
10 | class EsexMigrationManager extends MigrationManager {
11 | listAvailable() {
12 | return migrations
13 | .keys()
14 | .map(file => file.replace(/^\.\//, ''))
15 | .map(file => file.replace(/\.js$/, ''))
16 | }
17 |
18 | loadMigration(name) {
19 | return new Migration(migrations(`./${name}.js`))
20 | }
21 | }
22 |
23 | async function main() {
24 | let code = 0
25 |
26 | try {
27 | const manager = new EsexMigrationManager({
28 | db: orientoDb,
29 | dir: ''
30 | })
31 |
32 | const applied = await manager.up()
33 |
34 | applied.forEach(name => console.log(`Applied migration ${name}`)) // eslint-disable-line no-console
35 |
36 | } catch (error) {
37 | code = 1
38 | console.error(error.stack) // eslint-disable-line no-console
39 | }
40 |
41 | process.exit(code) // eslint-disable-line no-process-exit
42 | }
43 |
44 | main()
45 |
--------------------------------------------------------------------------------
/src/commands/webserver.js:
--------------------------------------------------------------------------------
1 | import 'source-map-support/register'
2 |
3 | import webserver from '../webserver'
4 | import config from '../webserver/di/config'
5 |
6 | webserver.listen(
7 | config.webserver.port,
8 | config.webserver.host
9 | )
10 |
--------------------------------------------------------------------------------
/src/components/AboutPage/AboutPage.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import Link from '../Link'
3 |
4 | function AboutPage({dispatcher}) {
5 | return (
6 |
7 |
About Page
8 |
9 | Home
10 |
11 |
12 | )
13 | }
14 |
15 | export default createObservableComponent(AboutPage)
16 |
--------------------------------------------------------------------------------
/src/components/AboutPage/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from './AboutPage'
4 |
--------------------------------------------------------------------------------
/src/components/Application/Application.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import * as pages from '../pages'
3 |
4 | function Application({dispatcher}) {
5 | return dispatcher.observable
6 | .map(state => ({
7 | Component: pages[state.router.componentKey],
8 | props: state.router.props
9 | }))
10 | .map(({Component, props}) => )
11 | }
12 |
13 | export default createObservableComponent(Application)
14 |
--------------------------------------------------------------------------------
/src/components/Application/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from './Application'
4 |
--------------------------------------------------------------------------------
/src/components/ErrorPage/ErrorPage.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 |
3 | function ErrorPage({errorMessage, errorStack}) {
4 | return (
5 |
6 |
Application Error
7 |
{errorMessage}
8 |
{errorStack}
9 |
10 | )
11 | }
12 |
13 | export default createObservableComponent(ErrorPage)
14 |
--------------------------------------------------------------------------------
/src/components/ErrorPage/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from './ErrorPage'
4 |
--------------------------------------------------------------------------------
/src/components/HomePage/HomePage.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import {fulfillTreeSchema, defineSchema, attribute} from 'components-data-tree'
3 | import getCurrentUser from '../../utilities/getCurrentUser'
4 | import bindAction from '../../utilities/bindAction'
5 | import preventEvent from '../../utilities/preventEvent'
6 | import {deauthorize} from '../../flux/token/tokenActions'
7 | import Link from '../Link'
8 | import SignIn from '../SignIn'
9 |
10 | const currentUserSchema = defineSchema({
11 | email: attribute
12 | })
13 |
14 | function DumbHomePage({dispatcher, currentUser, onSignOut}) {
15 | return (
16 |
17 |
Welcome
18 |
19 |
20 | {currentUser && `You are signed in as ${currentUser.email}`}
21 | {currentUser && }
22 |
23 |
24 | About
25 |
26 |
27 | Users
28 |
29 |
30 | )
31 | }
32 |
33 | function HomePage({dispatcher}) {
34 | return dispatcher.observable
35 | .map(state => ({
36 | dispatcher,
37 | onSignOut: bindAction(dispatcher, deauthorize),
38 | currentUser: fulfillTreeSchema(
39 | state.resources,
40 | getCurrentUser(
41 | state.resources,
42 | state.token
43 | ),
44 | currentUserSchema
45 | )
46 | }))
47 | .map(DumbHomePage)
48 | }
49 |
50 | export default createObservableComponent(HomePage)
51 |
--------------------------------------------------------------------------------
/src/components/HomePage/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from './HomePage'
4 |
--------------------------------------------------------------------------------
/src/components/Link/Link.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import {navigateTo} from '../../flux/router/routerActions'
3 |
4 | function Link(props) {
5 | const {dispatcher, href} = props
6 |
7 | // TODO: handle edge cases like ctrl+click
8 | const handleClick = event => {
9 | event.preventDefault()
10 | dispatcher.dispatch(navigateTo(href))
11 | }
12 |
13 | return (
14 |
15 | )
16 | }
17 |
18 | export default createObservableComponent(Link)
19 |
--------------------------------------------------------------------------------
/src/components/Link/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from './Link'
4 |
--------------------------------------------------------------------------------
/src/components/NotFoundPage/NotFoundPage.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 |
3 | function NotFoundPage() {
4 | return (
5 | 404 Not Found
6 | )
7 | }
8 |
9 | export default createObservableComponent(NotFoundPage)
10 |
--------------------------------------------------------------------------------
/src/components/NotFoundPage/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from './NotFoundPage'
4 |
--------------------------------------------------------------------------------
/src/components/SignIn/SignIn.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import {
3 | setEmail,
4 | setPassword,
5 | submit
6 | } from '../../flux/signIn/signInActions'
7 | import bindAction from '../../utilities/bindAction'
8 | import preventEvent from '../../utilities/preventEvent'
9 |
10 | function DumbSignIn(props) {
11 | const {dispatcher} = props
12 | const {data, validationMessagesVisible, validation, disabled, error} = props
13 | const {onEmailChange, onPasswordChange, onSubmit} = props
14 |
15 | const emailLink = {
16 | value: data.email,
17 | requestChange: onEmailChange
18 | }
19 |
20 | const passwordLink = {
21 | value: data.password,
22 | requestChange: onPasswordChange
23 | }
24 |
25 | return (
26 |
49 | )
50 | }
51 |
52 | function SignIn({dispatcher}) {
53 | return dispatcher.observable
54 | .map(state => ({
55 | dispatcher,
56 | ...state.signIn,
57 | onEmailChange: bindAction(dispatcher, setEmail),
58 | onPasswordChange: bindAction(dispatcher, setPassword),
59 | onSubmit: bindAction(dispatcher, submit)
60 | }))
61 | .map(DumbSignIn)
62 | }
63 |
64 | export default createObservableComponent(SignIn)
65 |
--------------------------------------------------------------------------------
/src/components/SignIn/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from './SignIn'
4 |
--------------------------------------------------------------------------------
/src/components/UsersPage/Item.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import {defineSchema, attribute} from 'components-data-tree'
3 |
4 | export const schema = defineSchema({
5 | id: attribute,
6 | title: attribute,
7 | price: attribute
8 | })
9 |
10 | function Item({item: {id, title, price}}) {
11 | return (
12 |
13 |
Item #{id}
14 |
title {title}
15 |
price {price}
16 |
17 | )
18 | }
19 |
20 | export default createObservableComponent(Item)
21 |
--------------------------------------------------------------------------------
/src/components/UsersPage/Order.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import {defineSchema, attribute, relationship} from 'components-data-tree'
3 | import Position, {schema as positionSchema} from './Position'
4 |
5 | export const schema = defineSchema({
6 | id: attribute,
7 | positions: relationship(positionSchema)
8 | })
9 |
10 | function Order({order: {id, positions}}) {
11 | return (
12 |
13 |
Order #{id}
14 | {positions.map((position, index) =>
)}
15 |
16 | )
17 | }
18 |
19 | export default createObservableComponent(Order)
20 |
--------------------------------------------------------------------------------
/src/components/UsersPage/Position.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import {defineSchema, attribute, relationship} from 'components-data-tree'
3 | import Item, {schema as itemSchema} from './Item'
4 |
5 | export const schema = defineSchema({
6 | id: attribute,
7 | amount: attribute,
8 | item: relationship(itemSchema)
9 | })
10 |
11 | function Position({position: {id, amount, item}}) {
12 | return (
13 |
14 |
Position #{id}
15 |
amount {amount}
16 |
17 |
18 | )
19 | }
20 |
21 | export default createObservableComponent(Position)
22 |
--------------------------------------------------------------------------------
/src/components/UsersPage/User.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import {defineSchema, attribute, relationship} from 'components-data-tree'
3 | import Order, {schema as orderSchema} from './Order'
4 |
5 | export const schema = defineSchema({
6 | id: attribute,
7 | email: attribute,
8 | password: attribute,
9 | orders: relationship(orderSchema)
10 | })
11 |
12 | function User({user: {id, email, password, orders}}) {
13 | return (
14 |
15 |
User #{id}
16 |
email {email}
17 |
password {password}
18 | {orders.map((order, index) =>
)}
19 |
20 | )
21 | }
22 |
23 | export default createObservableComponent(User)
24 |
--------------------------------------------------------------------------------
/src/components/UsersPage/UsersPage.js:
--------------------------------------------------------------------------------
1 | import createObservableComponent from 'react-observable'
2 | import {fulfillTreeSchema} from 'components-data-tree'
3 | import User, {schema as userSchema} from './User'
4 |
5 | function DumbUsersPage({users}) {
6 | return (
7 |
8 | {users.map((user, index) => )}
9 |
10 | )
11 | }
12 |
13 | function UsersPage({dispatcher, apiResponse}) {
14 | return dispatcher.observable
15 | .map(state => ({
16 | dispatcher,
17 | users: fulfillTreeSchema(
18 | state.resources,
19 | apiResponse.data,
20 | userSchema
21 | )
22 | }))
23 | .map(DumbUsersPage)
24 | }
25 |
26 | export default createObservableComponent(UsersPage)
27 |
--------------------------------------------------------------------------------
/src/components/UsersPage/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from './UsersPage'
4 |
--------------------------------------------------------------------------------
/src/components/pages.js:
--------------------------------------------------------------------------------
1 | export {default as HomePage} from './HomePage'
2 | export {default as AboutPage} from './AboutPage'
3 | export {default as NotFoundPage} from './NotFoundPage'
4 | export {default as ErrorPage} from './ErrorPage'
5 | export {default as UsersPage} from './UsersPage'
6 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | bcrypt: {
3 | rounds: process.env.BCRYPT_ROUNDS || 10
4 | },
5 | database: {
6 | host: process.env.DATABASE_HOST || 'localhost',
7 | port: process.env.DATABASE_PORT || '2424',
8 | user: process.env.DATABASE_USER || 'root',
9 | password: process.env.DATABASE_PASSWORD,
10 | dbname: process.env.DATABASE_DBNAME,
11 | dbuser: process.env.DATABASE_DBUSER,
12 | dbpassword: process.env.DATABASE_DBPASSWORD
13 | },
14 | webserver: {
15 | publicDir: process.env.PUBLIC_DIR,
16 | host: process.env.IP || '0.0.0.0',
17 | port: process.env.PORT || '3000'
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/config/routing.js:
--------------------------------------------------------------------------------
1 | export const notFound = {component: 'NotFoundPage', transition: 'notFound'}
2 | export const error = {component: 'ErrorPage', transition: 'error'}
3 |
4 | export default [
5 | {route: '/', component: 'HomePage', transition: 'home'},
6 | {route: '/about', component: 'AboutPage', transition: 'about'},
7 | {route: '/users', component: 'UsersPage', transition: 'users'}
8 | ]
9 |
--------------------------------------------------------------------------------
/src/fixtures/01-users.js:
--------------------------------------------------------------------------------
1 | import {range} from 'ramda'
2 | import bcrypt from 'bcryptjs'
3 | import config from '../webserver/di/config'
4 |
5 | export default async function(db) {
6 | const users = await* range(0, 3)
7 | .map(i => {
8 | const email = i + '@example.com'
9 | const password = bcrypt.hashSync('12345Wq', config.bcrypt.rounds)
10 |
11 | return db.insert().into('User').set({email, password}).one()
12 | })
13 |
14 | return {
15 | users
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/fixtures/02-items.js:
--------------------------------------------------------------------------------
1 | import {range} from 'ramda'
2 |
3 | export default async function(db) {
4 | const items = await* range(1, 4)
5 | .map(i => {
6 | const title = `Item #${i}`
7 | const price = i * 1000
8 |
9 | return db.insert().into('Item').set({title, price}).one()
10 | })
11 |
12 | return {
13 | items
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/fixtures/03-orders.js:
--------------------------------------------------------------------------------
1 | import {range, xprod} from 'ramda'
2 |
3 | export default async function(db, {users}) {
4 | const orders = await* xprod(users, range(1, 4))
5 | .map(async ([user, i]) => {
6 | const contactEmail = `${i}+${user.email}`
7 |
8 | const order = await db.insert().into('Order').set({contactEmail}).one()
9 |
10 | await db.create('EDGE', 'UserOrder').from(user['@rid']).to(order['@rid']).one()
11 |
12 | return order
13 | })
14 |
15 | return {
16 | orders
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/fixtures/04-orderpositions.js:
--------------------------------------------------------------------------------
1 | import {xprod} from 'ramda'
2 |
3 | export default async function(db, {orders, items}) {
4 | const orderPositions = await* xprod(orders, items)
5 | .map(([order, item]) => {
6 | const amount = Math.round(Math.random() * 99) + 1 // between 1 and 100
7 |
8 | return db.create('EDGE', 'OrderPosition').from(order['@rid']).to(item['@rid']).set({amount}).one()
9 | })
10 |
11 | return {
12 | orderPositions
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/flux/resources/resourcesActions.js:
--------------------------------------------------------------------------------
1 | import {
2 | RESOURCES_UPDATE
3 | } from './resourcesConstants'
4 |
5 | export function mergeResources(resources) {
6 | return {
7 | type: RESOURCES_UPDATE,
8 | resources
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/flux/resources/resourcesConstants.js:
--------------------------------------------------------------------------------
1 | export const RESOURCES_UPDATE = 'RESOURCES_UPDATE'
2 |
--------------------------------------------------------------------------------
/src/flux/resources/resourcesStore.js:
--------------------------------------------------------------------------------
1 | import {findIndex, merge} from 'ramda'
2 | import createStore from '../../utilities/createStore'
3 |
4 | import {
5 | RESOURCES_UPDATE
6 | } from './resourcesConstants'
7 |
8 | const initialState = []
9 |
10 | function mergeResources(resources, action) {
11 | resources = resources.concat()
12 |
13 | function mergeResource(resource) {
14 | const {type, id} = resource
15 |
16 | const index = findIndex(
17 | r => r.type === type && r.id === id,
18 | resources
19 | )
20 |
21 | if (index === -1) {
22 | resources.push(resource)
23 | return
24 | }
25 |
26 | const previousResource = resources[index]
27 |
28 | const attributes = merge(
29 | previousResource.attributes || {},
30 | resource.attributes || {}
31 | )
32 | const relationships = merge(
33 | previousResource.relationships || {},
34 | resource.relationships || {}
35 | )
36 |
37 | resources.splice(index, 1, {
38 | type,
39 | id,
40 | attributes,
41 | relationships
42 | })
43 | }
44 |
45 | action.resources.forEach(mergeResource)
46 |
47 | return resources
48 | }
49 |
50 | export default createStore(initialState, {
51 | [RESOURCES_UPDATE]: mergeResources
52 | })
53 |
--------------------------------------------------------------------------------
/src/flux/router/routerActions.js:
--------------------------------------------------------------------------------
1 | import routing, {notFound as notFoundRoute, error as errorRoute} from '../../config/routing'
2 | import * as transitions from '../transition/transitionActions'
3 | import progressIndicator from '../../utilities/progressIndicator'
4 | import matchRoutePattern from 'match-route-pattern'
5 |
6 | import {
7 | ROUTER_PAGE_CHANGE
8 | } from './routerConstants'
9 |
10 | function changePage(componentKey, props) {
11 | return {
12 | type: ROUTER_PAGE_CHANGE,
13 | componentKey,
14 | props
15 | }
16 | }
17 |
18 | function runNotFoundTransition(url) {
19 | return async dispatch => {
20 | const transition = transitions[notFoundRoute.transition]
21 | const componentKey = notFoundRoute.component
22 |
23 | const {title, status, ...props} = await dispatch(transition())
24 |
25 | dispatch(changePage(componentKey, props))
26 |
27 | return {url, title, status}
28 | }
29 | }
30 |
31 | function runErrorTransition(url, originalProps) {
32 | return async dispatch => {
33 | const transition = transitions[errorRoute.transition]
34 | const componentKey = errorRoute.component
35 |
36 | const {title, status, ...props} = await dispatch(transition())
37 |
38 | dispatch(changePage(componentKey, {...originalProps, ...props}))
39 |
40 | return {url, title, status}
41 | }
42 | }
43 |
44 | export function runTransition(url) {
45 | return async dispatch => {
46 | const route = routing
47 | .map(route => {
48 | return {...route, match: matchRoutePattern(route.route, url)}
49 | })
50 | .filter(({match}) => !!match)
51 | .shift()
52 |
53 | if (!route) {
54 | return dispatch(runNotFoundTransition(url))
55 | }
56 |
57 | const transition = transitions[route.transition]
58 | const componentKey = route.component
59 |
60 | const {title, status, ...props} = await dispatch(transition(route.match || {}))
61 |
62 | if (status === 401) {
63 | if (global.history) { // TODO: fix this shit
64 | dispatch(navigateTo('/'))
65 | }
66 | } else if (status === 404) {
67 | return dispatch(runNotFoundTransition(url))
68 | } else if (status < 200 || status >= 300) {
69 | return dispatch(runErrorTransition(url, props))
70 | } else {
71 | dispatch(changePage(componentKey, props))
72 | }
73 |
74 | return {url, title, status}
75 | }
76 | }
77 |
78 | export function navigateTo(url) {
79 | return async dispatch => {
80 | await progressIndicator(async () => {
81 | const {title} = await dispatch(runTransition(url))
82 |
83 | document.title = title
84 | history.pushState({url, title}, title, url)
85 | })
86 | }
87 | }
88 |
89 | export function popState(event) {
90 | return async dispatch => {
91 | if (!event.state || !event.state.url) {
92 | window.location.reload()
93 | return
94 | }
95 |
96 | const {url, title} = event.state
97 |
98 | document.title = title
99 |
100 | await progressIndicator(async () => {
101 | await dispatch(runTransition(url))
102 | })
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/flux/router/routerConstants.js:
--------------------------------------------------------------------------------
1 | export const ROUTER_PAGE_CHANGE = 'ROUTER_PAGE_CHANGE'
2 |
--------------------------------------------------------------------------------
/src/flux/router/routerStore.js:
--------------------------------------------------------------------------------
1 | import createStore from '../../utilities/createStore'
2 |
3 | import {
4 | ROUTER_PAGE_CHANGE
5 | } from './routerConstants'
6 |
7 | const initialState = {
8 | componentKey: undefined,
9 | props: undefined
10 | }
11 |
12 | function page(state, {componentKey, props}) {
13 | return {componentKey, props}
14 | }
15 |
16 | export default createStore(initialState, {
17 | [ROUTER_PAGE_CHANGE]: page
18 | })
19 |
--------------------------------------------------------------------------------
/src/flux/signIn/signInActions.js:
--------------------------------------------------------------------------------
1 | import {authorize} from '../token/tokenActions'
2 |
3 | import {
4 | SIGN_IN_EMAIL_CHANGE,
5 | SIGN_IN_PASSWORD_CHANGE,
6 | SIGN_IN_VALIDATION_MESSAGES_VISIBLE,
7 | SIGN_IN_ERROR,
8 | SIGN_IN_DISABLED,
9 | SIGN_IN_RESET
10 | } from './signInConstants'
11 |
12 | export function setEmail(email) {
13 | return {
14 | type: SIGN_IN_EMAIL_CHANGE,
15 | email
16 | }
17 | }
18 |
19 | export function setPassword(password) {
20 | return {
21 | type: SIGN_IN_PASSWORD_CHANGE,
22 | password
23 | }
24 | }
25 |
26 | export function setValidationMessagesVisible(visible) {
27 | return {
28 | type: SIGN_IN_VALIDATION_MESSAGES_VISIBLE,
29 | visible
30 | }
31 | }
32 |
33 | export function setError(error) {
34 | return {
35 | type: SIGN_IN_ERROR,
36 | error
37 | }
38 | }
39 |
40 | export function setDisabled(disabled) {
41 | return {
42 | type: SIGN_IN_DISABLED,
43 | disabled
44 | }
45 | }
46 |
47 | export function reset() {
48 | return {
49 | type: SIGN_IN_RESET
50 | }
51 | }
52 |
53 | export function submit() {
54 | return async (dispatch, getState) => {
55 | const {signIn} = getState()
56 | const {disabled, validation: {valid}, data} = signIn
57 | const {email, password} = data
58 |
59 | if (disabled) {
60 | return
61 | }
62 |
63 | if (!valid) {
64 | dispatch(setValidationMessagesVisible(true))
65 | return
66 | }
67 |
68 | dispatch(setDisabled(true))
69 | dispatch(setError(undefined))
70 | dispatch(setValidationMessagesVisible(false))
71 |
72 | try {
73 | await dispatch(authorize(
74 | email,
75 | password
76 | ))
77 | dispatch(reset())
78 | } catch (error) {
79 | dispatch(setError(error))
80 | } finally {
81 | dispatch(setDisabled(false))
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/flux/signIn/signInConstants.js:
--------------------------------------------------------------------------------
1 | export const SIGN_IN_EMAIL_CHANGE = 'SIGN_IN_EMAIL_CHANGE'
2 | export const SIGN_IN_PASSWORD_CHANGE = 'SIGN_IN_PASSWORD_CHANGE'
3 | export const SIGN_IN_VALIDATION_MESSAGES_VISIBLE = 'SIGN_IN_VALIDATION_MESSAGES_VISIBLE'
4 | export const SIGN_IN_ERROR = 'SIGN_IN_ERROR'
5 | export const SIGN_IN_DISABLED = 'SIGN_IN_DISABLED'
6 | export const SIGN_IN_RESET = 'SIGN_IN_RESET'
7 |
--------------------------------------------------------------------------------
/src/flux/signIn/signInStore.js:
--------------------------------------------------------------------------------
1 | import {merge} from 'ramda'
2 | import createStore from '../../utilities/createStore'
3 | import signInValidator from './signInValidator'
4 |
5 | import {
6 | SIGN_IN_EMAIL_CHANGE,
7 | SIGN_IN_PASSWORD_CHANGE,
8 | SIGN_IN_VALIDATION_MESSAGES_VISIBLE,
9 | SIGN_IN_ERROR,
10 | SIGN_IN_DISABLED,
11 | SIGN_IN_RESET
12 | } from './signInConstants'
13 |
14 | const initialData = {
15 | email: '',
16 | password: ''
17 | }
18 |
19 | const initialState = {
20 | data: initialData,
21 | validationMessagesVisible: false,
22 | validation: signInValidator(initialData),
23 | error: undefined,
24 | disabled: false
25 | }
26 |
27 | function mergeData(state, changes) {
28 | const data = merge(state.data, changes)
29 | const validation = signInValidator(data)
30 |
31 | return merge(state, {data, validation})
32 | }
33 |
34 | function email(state, {email}) {
35 | return mergeData(state, {email})
36 | }
37 |
38 | function password(state, {password}) {
39 | return mergeData(state, {password})
40 | }
41 |
42 | function validationMessagesVisible(state, {visible}) {
43 | return merge(state, {validationMessagesVisible: visible})
44 | }
45 |
46 | function error(state, {error}) {
47 | return merge(state, {error})
48 | }
49 |
50 | function disabled(state, {disabled}) {
51 | return merge(state, {disabled})
52 | }
53 |
54 | export default createStore(initialState, {
55 | [SIGN_IN_EMAIL_CHANGE]: email,
56 | [SIGN_IN_PASSWORD_CHANGE]: password,
57 | [SIGN_IN_VALIDATION_MESSAGES_VISIBLE]: validationMessagesVisible,
58 | [SIGN_IN_ERROR]: error,
59 | [SIGN_IN_DISABLED]: disabled,
60 | [SIGN_IN_RESET]: () => initialState
61 | })
62 |
--------------------------------------------------------------------------------
/src/flux/signIn/signInValidator.js:
--------------------------------------------------------------------------------
1 | import {constraints} from 'strulidator'
2 | import User from '../../schemas/User'
3 |
4 | const {createObjectConstraint} = constraints
5 |
6 | export default createObjectConstraint({
7 | email: User.attributes.email,
8 | password: User.attributes.password
9 | })
10 |
--------------------------------------------------------------------------------
/src/flux/stores.js:
--------------------------------------------------------------------------------
1 | export {default as router} from './router/routerStore'
2 | export {default as token} from './token/tokenStore'
3 | export {default as resources} from './resources/resourcesStore'
4 | export {default as signIn} from './signIn/signInStore'
5 |
--------------------------------------------------------------------------------
/src/flux/token/tokenActions.js:
--------------------------------------------------------------------------------
1 | import apiRequest from '../../utilities/apiRequest'
2 | import collectResources from '../../utilities/collectResources'
3 | import {mergeResources} from '../resources/resourcesActions'
4 | import {
5 | TOKEN
6 | } from './tokenConstants'
7 |
8 | export function setToken(token) {
9 | return {
10 | type: TOKEN,
11 | token
12 | }
13 | }
14 |
15 | export function authorize(username, password) {
16 | return async dispatch => {
17 | const response = await apiRequest('/xhr/login?include=user', {
18 | method: 'POST',
19 | headers: {
20 | 'Authorization': `Basic ${btoa(`${username}:${password}`)}` // TODO: use polyfill?
21 | }
22 | })
23 |
24 | dispatch(setToken(response.data.attributes.hash))
25 | dispatch(mergeResources(collectResources(response)))
26 | }
27 | }
28 |
29 | export function deauthorize() {
30 | return async (dispatch, getState) => {
31 | const {token} = getState()
32 |
33 | if (!token) {
34 | return
35 | }
36 |
37 | await apiRequest('/xhr/logout', {
38 | method: 'POST',
39 | headers: {
40 | 'Authorization': `Bearer ${token}`
41 | }
42 | })
43 |
44 | dispatch(setToken(undefined))
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/flux/token/tokenConstants.js:
--------------------------------------------------------------------------------
1 | export const TOKEN = 'TOKEN'
2 |
--------------------------------------------------------------------------------
/src/flux/token/tokenStore.js:
--------------------------------------------------------------------------------
1 | import createStore from '../../utilities/createStore'
2 |
3 | import {
4 | TOKEN
5 | } from './tokenConstants'
6 |
7 | function newToken(previousToken, {token}) {
8 | return token
9 | }
10 |
11 | export default createStore(undefined, {
12 | [TOKEN]: newToken
13 | })
14 |
--------------------------------------------------------------------------------
/src/flux/transition/createFetchTransition.js:
--------------------------------------------------------------------------------
1 | import {mergeResources} from '../resources/resourcesActions'
2 | import collectResources from '../../utilities/collectResources'
3 |
4 | export default function createFetchTransition({status = 200, title, apiRequest}) {
5 | return function transition(queryParams) {
6 | return async (dispatch, getState) => {
7 | const {token} = getState()
8 |
9 | const result = {
10 | status,
11 | queryParams
12 | }
13 |
14 | try {
15 | result.apiResponse = (await apiRequest(queryParams, token)) || {}
16 |
17 | dispatch(mergeResources(collectResources(result.apiResponse)))
18 |
19 | } catch (error) {
20 | result.status = error.responseStatus || 500
21 | result.errorMessage = error.message
22 | result.errorStack = error.stack
23 | }
24 |
25 | // TODO: 404 500 error title
26 | result.title = typeof title === 'function'
27 | ? title(result)
28 | : title
29 |
30 | return result
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/flux/transition/createStaticTransition.js:
--------------------------------------------------------------------------------
1 | export default function createStaticTransition({status = 200, title, ...other}) {
2 | return function transition(queryParams = {}) {
3 | return () => {
4 | return {
5 | status,
6 | title,
7 | queryParams,
8 | ...other
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/flux/transition/transitionActions.js:
--------------------------------------------------------------------------------
1 | import * as api from '../../api'
2 | import createFetchTransition from './createFetchTransition'
3 | import createStaticTransition from './createStaticTransition'
4 |
5 | export const home = createStaticTransition({
6 | title: 'Home'
7 | })
8 |
9 | export const about = createStaticTransition({
10 | title: 'About'
11 | })
12 |
13 | export const users = createFetchTransition({
14 | title: 'Users',
15 | apiRequest: api.usersPageApiRequest
16 | })
17 |
18 | export const notFound = createStaticTransition({
19 | status: 404,
20 | title: 'Not Found'
21 | })
22 |
23 | export const error = createStaticTransition({
24 | status: 500,
25 | title: 'Application Error'
26 | })
27 |
--------------------------------------------------------------------------------
/src/frontend/index.js:
--------------------------------------------------------------------------------
1 | import './polyfills'
2 |
3 | import React from 'react'
4 | import Application from '../components/Application'
5 | import createDispatcher from '../utilities/createDispatcher'
6 | import {popState} from '../flux/router/routerActions'
7 |
8 | import 'nprogress/nprogress.css'
9 |
10 | function bootstrapDispatcher(initialState) {
11 | const dispatcher = createDispatcher(initialState)
12 |
13 | const url = document.location.pathname + document.location.search
14 | const title = document.title
15 |
16 | history.replaceState({url, title}, title, url)
17 |
18 | window.addEventListener('popstate', event => {
19 | dispatcher.dispatch(popState(event))
20 | })
21 |
22 | return dispatcher
23 | }
24 |
25 | export function bootstrapApplication(container, initialState) {
26 | const dispatcher = bootstrapDispatcher(initialState)
27 |
28 | React.render(
29 | ,
30 | container
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/src/frontend/polyfills.js:
--------------------------------------------------------------------------------
1 | import '../utilities/patchReact'
2 | import 'html5-history-api'
3 | import 'whatwg-fetch'
4 |
--------------------------------------------------------------------------------
/src/migrations/m20150624_180904_user.js:
--------------------------------------------------------------------------------
1 | export async function up(db) {
2 | const cls = await db.class.create('User', 'V')
3 |
4 | await cls.property.create({
5 | name: 'email',
6 | type: 'string',
7 | mandatory: true,
8 | notNull: true
9 | })
10 |
11 | await cls.property.create({
12 | name: 'password',
13 | type: 'string',
14 | mandatory: true,
15 | notNull: true
16 | })
17 |
18 | await db.index.create({
19 | name: 'User.email',
20 | type: 'unique'
21 | })
22 | }
23 |
24 | export async function down(db) {
25 | await db.exec('DROP INDEX User.email')
26 | await db.exec('TRUNCATE CLASS User')
27 | await db.exec('DROP CLASS User')
28 | }
29 |
--------------------------------------------------------------------------------
/src/migrations/m20150625_113856_item.js:
--------------------------------------------------------------------------------
1 | export async function up(db) {
2 | const cls = await db.class.create('Item', 'V')
3 |
4 | await cls.property.create({
5 | name: 'title',
6 | type: 'string',
7 | mandatory: true,
8 | notNull: true
9 | })
10 |
11 | await cls.property.create({
12 | name: 'price',
13 | type: 'decimal',
14 | mandatory: true,
15 | notNull: true,
16 | min: 0
17 | })
18 | }
19 |
20 | export async function down(db) {
21 | await db.exec('TRUNCATE CLASS Item')
22 | await db.exec('DROP CLASS Item')
23 | }
24 |
--------------------------------------------------------------------------------
/src/migrations/m20150625_113950_order.js:
--------------------------------------------------------------------------------
1 | export async function up(db) {
2 | const cls = await db.class.create('Order', 'V')
3 |
4 | await cls.property.create({
5 | name: 'contactEmail',
6 | type: 'string',
7 | mandatory: true,
8 | notNull: true
9 | })
10 | }
11 |
12 | export async function down(db) {
13 | await db.exec('TRUNCATE CLASS Order')
14 | await db.exec('DROP CLASS Order')
15 | }
16 |
--------------------------------------------------------------------------------
/src/migrations/m20150625_114024_userorder.js:
--------------------------------------------------------------------------------
1 | export async function up(db) {
2 | const cls = await db.class.create('UserOrder', 'E')
3 |
4 | await cls.property.create({
5 | name: 'out',
6 | type: 'link',
7 | mandatory: true,
8 | notNull: true,
9 | linkedClass: 'User'
10 | })
11 |
12 | await cls.property.create({
13 | name: 'in',
14 | type: 'link',
15 | mandatory: true,
16 | notNull: true,
17 | linkedClass: 'Order'
18 | })
19 | }
20 |
21 | export async function down(db) {
22 | await db.exec('TRUNCATE CLASS UserOrder')
23 | await db.exec('DROP CLASS UserOrder')
24 | }
25 |
--------------------------------------------------------------------------------
/src/migrations/m20150625_114102_orderposition.js:
--------------------------------------------------------------------------------
1 | export async function up(db) {
2 | const cls = await db.class.create('OrderPosition', 'E')
3 |
4 | await cls.property.create({
5 | name: 'out',
6 | type: 'link',
7 | mandatory: true,
8 | notNull: true,
9 | linkedClass: 'Order'
10 | })
11 |
12 | await cls.property.create({
13 | name: 'in',
14 | type: 'link',
15 | mandatory: true,
16 | notNull: true,
17 | linkedClass: 'Item'
18 | })
19 |
20 | await cls.property.create({
21 | name: 'amount',
22 | type: 'integer',
23 | mandatory: true,
24 | notNull: true,
25 | min: 1
26 | })
27 |
28 | await db.index.create({
29 | name: 'OrderPosition.out_in',
30 | class: 'OrderPosition',
31 | properties: ['out', 'in'],
32 | type: 'unique'
33 | })
34 | }
35 |
36 | export async function down(db) {
37 | await db.exec('DROP INDEX OrderPosition.out_in')
38 | await db.exec('TRUNCATE CLASS OrderPosition')
39 | await db.exec('DROP CLASS OrderPosition')
40 | }
41 |
--------------------------------------------------------------------------------
/src/migrations/m20150625_161547_accesstoken.js:
--------------------------------------------------------------------------------
1 | export async function up(db) {
2 | const cls = await db.class.create('AccessToken', 'V')
3 |
4 | await cls.property.create({
5 | name: 'hash',
6 | type: 'string',
7 | mandatory: true,
8 | notNull: true
9 | })
10 |
11 | await db.index.create({
12 | name: 'AccessToken.hash',
13 | type: 'unique'
14 | })
15 | }
16 |
17 | export async function down(db) {
18 | await db.exec('DROP INDEX AccessToken.hash')
19 | await db.exec('TRUNCATE CLASS AccessToken')
20 | await db.exec('DROP CLASS AccessToken')
21 | }
22 |
--------------------------------------------------------------------------------
/src/migrations/m20150625_161713_usertoken.js:
--------------------------------------------------------------------------------
1 | export async function up(db) {
2 | const cls = await db.class.create('UserToken', 'E')
3 |
4 | await cls.property.create({
5 | name: 'out',
6 | type: 'link',
7 | mandatory: true,
8 | notNull: true,
9 | linkedClass: 'User'
10 | })
11 |
12 | await cls.property.create({
13 | name: 'in',
14 | type: 'link',
15 | mandatory: true,
16 | notNull: true,
17 | linkedClass: 'AccessToken'
18 | })
19 | }
20 |
21 | export async function down(db) {
22 | await db.exec('TRUNCATE CLASS UserToken')
23 | await db.exec('DROP CLASS UserToken')
24 | }
25 |
--------------------------------------------------------------------------------
/src/schemas/AccessToken.js:
--------------------------------------------------------------------------------
1 | import {constraints} from 'strulidator'
2 |
3 | const {
4 | combineConstraints,
5 | notNull,
6 | notEmpty,
7 | string
8 | } = constraints
9 |
10 | export default {
11 | type: 'AccessToken',
12 | attributes: {
13 | hash: combineConstraints({
14 | notNull,
15 | notEmpty,
16 | string
17 | })
18 | },
19 | relationships: {
20 | user: {
21 | class: 'User',
22 | reverseThroughEdge: 'UserToken'
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/schemas/Item.js:
--------------------------------------------------------------------------------
1 | import {constraints} from 'strulidator'
2 |
3 | const {
4 | combineConstraints,
5 | notNull,
6 | notEmpty,
7 | string,
8 | number,
9 | createMinConstraint,
10 | createMinLengthConstraint
11 | } = constraints
12 |
13 | export default {
14 | type: 'Item',
15 | attributes: {
16 | title: combineConstraints({
17 | notNull,
18 | notEmpty,
19 | string,
20 | minLength: createMinLengthConstraint(3)
21 | }),
22 | price: combineConstraints({
23 | notNull,
24 | number,
25 | min: createMinConstraint(0)
26 | })
27 | },
28 | relationships: {
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/schemas/Order.js:
--------------------------------------------------------------------------------
1 | export default {
2 | type: 'Order',
3 | attributes: {
4 | },
5 | relationships: {
6 | positions: {
7 | class: 'OrderPosition'
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/schemas/OrderPosition.js:
--------------------------------------------------------------------------------
1 | import {constraints} from 'strulidator'
2 |
3 | const {
4 | combineConstraints,
5 | notNull,
6 | number,
7 | createMinConstraint
8 | } = constraints
9 |
10 | export default {
11 | type: 'OrderPosition',
12 | attributes: {
13 | amount: combineConstraints({
14 | notNull,
15 | number,
16 | min: createMinConstraint(0)
17 | })
18 | },
19 | relationships: {
20 | item: {
21 | class: 'Item',
22 | throughEdge: 'OrderPosition'
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/schemas/User.js:
--------------------------------------------------------------------------------
1 | import {constraints} from 'strulidator'
2 | import {merge} from 'ramda'
3 | import bcrypt from 'bcryptjs' // TODO: use only in webserver environment
4 | import {authorized} from 'access-rule'
5 | import config from '../config'
6 |
7 | const {
8 | combineConstraints,
9 | notNull,
10 | notEmpty,
11 | string,
12 | email,
13 | createMinLengthConstraint,
14 | createMaxLengthConstraint
15 | } = constraints
16 |
17 | export default {
18 | type: 'User',
19 | attributes: {
20 | email: combineConstraints({
21 | notNull,
22 | notEmpty,
23 | string,
24 | email
25 | }),
26 | password: combineConstraints({
27 | notNull,
28 | notEmpty,
29 | string,
30 | minLength: createMinLengthConstraint(5),
31 | maxLength: createMaxLengthConstraint(18)
32 | })
33 | },
34 | relationships: {
35 | orders: {
36 | class: 'Order',
37 | throughEdge: 'UserOrder'
38 | }
39 | },
40 | schemaAccessRule: authorized,
41 | resourceAccessRule: authorized,
42 | normalize: user => {
43 | if (!user.attributes.password) {
44 | return user
45 | }
46 |
47 | const password = bcrypt.hashSync(user.attributes.password, config.bcrypt.rounds)
48 |
49 | return merge(user, {
50 | attributes: merge(user.attributes, {password})
51 | })
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/schemas/index.js:
--------------------------------------------------------------------------------
1 | export {default as User} from './User'
2 | export {default as Item} from './Item'
3 | export {default as Order} from './Order'
4 | export {default as OrderPosition} from './OrderPosition'
5 | export {default as AccessToken} from './AccessToken'
6 |
--------------------------------------------------------------------------------
/src/utilities/apiRequest.js:
--------------------------------------------------------------------------------
1 | export default async function apiRequest(url, spec) {
2 | const {method = 'GET', body} = spec
3 | const {fields = {}, include = []} = spec
4 | const {token} = spec
5 | const {headers = {}} = spec
6 |
7 | let queryParams = Object.keys(fields)
8 | .reduce((acc, key) => {
9 | return [...acc, `fields[${key}]=${fields[key].join(',')}`]
10 | }, [])
11 |
12 | if (include.length > 0) {
13 | queryParams = queryParams.concat([`include=${include.join(',')}`])
14 | }
15 |
16 | let apiUrl = `${process.env.BACKEND_ADDRESS}${url}`
17 |
18 | if (queryParams) {
19 | if (apiUrl.indexOf('?') === -1) {
20 | apiUrl = `${apiUrl}?`
21 | } else {
22 | apiUrl = `${apiUrl}&`
23 | }
24 | apiUrl = `${apiUrl}${queryParams.join('&')}`
25 | }
26 |
27 | headers['Accept'] = 'application/vnd.api+json'
28 | headers['Content-Type'] = 'application/vnd.api+json'
29 |
30 | if (token) {
31 | headers['Authorization'] = `Bearer ${token}`
32 | }
33 |
34 | const response = await fetch(apiUrl, {
35 | credentials: 'include',
36 | method,
37 | body: body && JSON.stringify(body),
38 | headers
39 | })
40 |
41 | if (response.status < 200 || response.status >= 300) {
42 | let errorMessage
43 | let errorStack
44 | let json
45 |
46 | try {
47 | json = await response.json()
48 | } catch (error) {
49 | errorMessage = response.statusText
50 | }
51 |
52 | if (json && json.errors && json.errors.length > 0) {
53 | errorMessage = json.errors[0].title
54 | errorStack = json.errors[0].detail
55 | } else {
56 | errorMessage = response.statusText
57 | }
58 |
59 | {
60 | const error = new Error('API Error: ' + errorMessage)
61 |
62 | error.responseStatus = response.status
63 |
64 | if (errorStack) {
65 | error.stack = errorStack
66 | }
67 |
68 | throw error
69 | }
70 | }
71 |
72 | const json = await response.json()
73 |
74 | return json
75 | }
76 |
--------------------------------------------------------------------------------
/src/utilities/bindAction.js:
--------------------------------------------------------------------------------
1 | export default function bindAction(dispatcher, action) {
2 | return (...args) => {
3 | return dispatcher.dispatch(action(...args))
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/utilities/collectResources.js:
--------------------------------------------------------------------------------
1 | export default function collectResources(apiResponse) {
2 | let resources = []
3 |
4 | if (Array.isArray(apiResponse.data)) {
5 | resources = resources.concat(apiResponse.data)
6 | } else if (apiResponse.data) {
7 | resources.push(apiResponse.data)
8 | }
9 |
10 | if (apiResponse.included) {
11 | resources = resources.concat(apiResponse.included)
12 | }
13 |
14 | return resources
15 | }
16 |
--------------------------------------------------------------------------------
/src/utilities/createDispatcher.js:
--------------------------------------------------------------------------------
1 | import {createRedux} from 'redux'
2 | import * as stores from '../flux/stores'
3 | import Rx from 'rx'
4 |
5 | export default function createDispatcher(initialState) {
6 | const dispatcher = createRedux(stores, initialState)
7 | const {subscribe, dispatch, getState} = dispatcher
8 |
9 | const observable = Rx.Observable.create(observer => {
10 | const listener = () => observer.onNext(getState())
11 | const unsubscribe = subscribe(listener)
12 |
13 | listener()
14 |
15 | return unsubscribe
16 | })
17 |
18 | return {
19 | observable,
20 | dispatch,
21 | getState
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/utilities/createStore.js:
--------------------------------------------------------------------------------
1 | export default function createStore(initialState, handlers) {
2 | return (state = initialState, action) => {
3 | const handler = handlers[action.type]
4 |
5 | return handler ? handler(state, action) : state
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/utilities/getCurrentUser.js:
--------------------------------------------------------------------------------
1 | export default function getCurrentUser(resources, token) {
2 | if (!token) {
3 | return
4 | }
5 |
6 | const tokenResource = resources
7 | .filter(resource => resource.type === 'AccessToken')
8 | .filter(resource => resource.attributes.hash === token)
9 | .shift()
10 |
11 | if (!tokenResource) {
12 | return
13 | }
14 |
15 | const userId = tokenResource.relationships.user.data[0].id // TODO: fix
16 |
17 | const userResource = resources
18 | .filter(resource => resource.type === 'User' && resource.id === userId)
19 | .shift()
20 |
21 | return userResource
22 | }
23 |
--------------------------------------------------------------------------------
/src/utilities/patchReact.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // TODO: remove after https://github.com/facebook/react/pull/3995
4 |
5 | const originalCreateElement = React.createElement
6 |
7 | React.createElement = function createElement(element, ...rest) {
8 | let type = element
9 |
10 | if (typeof element === 'function' && !element.prototype.render) {
11 | if (!element.ReactComponent) {
12 | element.ReactComponent = class Component {
13 | static displayName = element.name
14 |
15 | render() {
16 | return element(this.props)
17 | }
18 | }
19 | }
20 |
21 | type = element.ReactComponent
22 | }
23 |
24 | return originalCreateElement(type, ...rest)
25 | }
26 |
--------------------------------------------------------------------------------
/src/utilities/preventEvent.js:
--------------------------------------------------------------------------------
1 | export default function preventEvent(callback) {
2 | return event => {
3 | event.preventDefault()
4 | callback()
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/utilities/progressIndicator.js:
--------------------------------------------------------------------------------
1 | import NProgress from 'nprogress'
2 |
3 | export default async function progressIndicator(executor) {
4 | NProgress.start()
5 | NProgress.set(0.3)
6 |
7 | const interval = setInterval(() => NProgress.inc(), 500)
8 |
9 | try {
10 | return await executor()
11 | } finally {
12 | clearInterval(interval)
13 | NProgress.done()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/webserver/db/applyTransaction.js:
--------------------------------------------------------------------------------
1 | export default function applyTransaction(db, {steps, returnKey, returnType}) {
2 | let query = db
3 |
4 | if (returnType !== 'all' && returnType !== 'one') {
5 | throw new Error(`Unexpected return type "${returnType}", expected "all" or "one"`)
6 | }
7 |
8 | for (const step of steps) {
9 | switch (step.type) {
10 | case 'createVertex':
11 | query = query
12 | .let(step.key, s => {
13 | s.insert().into(step.class).set(step.attributes)
14 | })
15 | break
16 |
17 | case 'updateVertex':
18 | query = query
19 | .let(step.key, s => {
20 | s.update(step.class).set(step.attributes).where({'@rid': step.id})
21 | })
22 | break
23 |
24 | case 'removeEdges':
25 | query = query
26 | .let(step.key, s => {
27 | s.delete('EDGE', step.class).where({'out': step.from})
28 | })
29 | break
30 |
31 | case 'findOne':
32 | query = query
33 | .let(step.key, s => {
34 | s.select().from(step.class).where({'@rid': step.id})
35 | })
36 | break
37 |
38 | case 'createEdge':
39 | query = query
40 | .let(step.key, s => {
41 | s.create('edge', step.class).from(step.from).to(step.to)
42 | })
43 | break
44 |
45 | default:
46 | throw new Error(`Unknown transaction step "${step.type}"`)
47 | }
48 | }
49 |
50 | query = query
51 | .return(returnKey)
52 | .commit()
53 |
54 | return returnType === 'one'
55 | ? query.one()
56 | : query.all()
57 | }
58 |
--------------------------------------------------------------------------------
/src/webserver/db/errors.js:
--------------------------------------------------------------------------------
1 | export class ParametersError {
2 | constructor(message) {
3 | this.message = message
4 | this.stack = (new Error()).stack
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/webserver/db/fulfillQuery.js:
--------------------------------------------------------------------------------
1 | import {uniqWith, flatten} from 'ramda'
2 | import {ParametersError} from './errors'
3 | import mapObjectToResource from './mapObjectToResource'
4 | import * as schemas from '../../schemas'
5 |
6 | function objectToResource(db, object, fields) {
7 | return mapObjectToResource(db, object, fields[object['@class']])
8 | }
9 |
10 | function reduceOuts({schema, queryPath}, field) {
11 | if (!schema.relationships || !schema.relationships[field]) {
12 | throw new ParametersError(`Class "${schema.type}" haven't relationship "${field}"`)
13 | }
14 |
15 | const relationship = schema.relationships[field]
16 |
17 | if (relationship.throughEdge === schema.type) {
18 | queryPath.pop()
19 | queryPath.push(`OUT('${relationship.throughEdge}')`)
20 |
21 | } else if (relationship.throughEdge) {
22 | queryPath.push(`OUT('${relationship.throughEdge}')`)
23 |
24 | } else if (relationship.reverseThroughEdge) {
25 | queryPath.push(`IN('${relationship.reverseThroughEdge}')`)
26 |
27 | } else if (relationship.class) {
28 | queryPath.push(`OUTE('${relationship.class}')`)
29 |
30 | } else {
31 | throw new Error(`Invalid relationship definition "${field}" in class "${schema.type}"`)
32 | }
33 |
34 | return {
35 | schema: schemas[relationship.class],
36 | queryPath
37 | }
38 | }
39 |
40 | async function fetchInclude({db, object, path, fields}) {
41 | const type = object['@class']
42 | const id = object['@rid']
43 | const classSchema = schemas[type]
44 |
45 | const queryPath = path
46 | .reduce(reduceOuts, {schema: classSchema, queryPath: []})
47 | .queryPath
48 | .join('.')
49 |
50 | const query = `SELECT EXPAND(${queryPath}) FROM ${type} WHERE @rid = ${id}`
51 | const objects = await db.query(query)
52 | const resources = await* objects.map(object => objectToResource(db, object, fields))
53 |
54 | return resources
55 | }
56 |
57 |
58 | export default async function fulfillQuery(db, root, {fields, include}) {
59 | if (!root) {
60 | return {data: null} // null-reasonable
61 | }
62 |
63 | const data = Array.isArray(root)
64 | ? (await * root.map(object => objectToResource(db, object, fields)))
65 | : (await objectToResource(db, root, fields))
66 |
67 | const response = {
68 | data
69 | }
70 |
71 | const objects = Array.isArray(root) ? root : [root]
72 | let included = []
73 |
74 | for (const oneInclude of include) {
75 | const path = oneInclude.split('.')
76 |
77 | const includeResources = await* objects.map(object =>
78 | fetchInclude({db, object, path, fields})
79 | )
80 |
81 | included = included.concat(flatten(includeResources))
82 | }
83 |
84 | included = uniqWith(
85 | (a, b) => a.type === b.type && a.id === b.id,
86 | included
87 | )
88 |
89 | if (included.length > 0) {
90 | response.included = included
91 | }
92 |
93 | return response
94 | }
95 |
--------------------------------------------------------------------------------
/src/webserver/db/mapObjectToResource.js:
--------------------------------------------------------------------------------
1 | import {ParametersError} from './errors'
2 | import * as schemas from '../../schemas'
3 |
4 | function prepareId(id) {
5 | return id.toString().replace(/^#/, '')
6 | }
7 |
8 | async function fetchRelationshipDataThroughIn({db, relationship, type, id}) {
9 | const links = await db.query(`SELECT in FROM ${type} WHERE @rid = ${id}`)
10 |
11 | if (!links[0]) {
12 | return
13 | }
14 |
15 | return {type: relationship.class, id: prepareId(links[0].in)}
16 | }
17 |
18 | async function fetchRelationshipDataThroughEdge({db, relationship, type, id, reverse = false}) {
19 | const edgeClass = reverse ? relationship.reverseThroughEdge : relationship.throughEdge
20 | const fn = reverse ? 'IN' : 'OUT'
21 |
22 | const links = await db.query(`SELECT ${fn}('${edgeClass}') FROM ${type} WHERE @rid = ${id}`)
23 |
24 | if (!links[0]) {
25 | return []
26 | }
27 |
28 | return links[0][fn].map(linkId => ({type: relationship.class, id: prepareId(linkId)}))
29 | }
30 |
31 | async function fetchRelationshipDataEdge({db, relationship, type, id}) {
32 | const links = await db.query(`SELECT OUTE('${relationship.class}') FROM ${type} WHERE @rid = ${id}`)
33 |
34 | if (!links[0]) {
35 | return []
36 | }
37 |
38 | return links[0].OUTE.map(linkId => ({type: relationship.class, id: prepareId(linkId)}))
39 | }
40 |
41 | async function fetchRelationshipData({db, relationship, type, id, field}) {
42 | if (relationship.throughEdge === type) {
43 | return await fetchRelationshipDataThroughIn({db, relationship, type, id})
44 | }
45 |
46 | if (relationship.throughEdge) {
47 | return await fetchRelationshipDataThroughEdge({db, relationship, type, id})
48 | }
49 |
50 | if (relationship.reverseThroughEdge) {
51 | return await fetchRelationshipDataThroughEdge({db, relationship, type, id, reverse: true})
52 | }
53 |
54 | if (relationship.class) {
55 | return await fetchRelationshipDataEdge({db, relationship, type, id})
56 | }
57 |
58 | throw new Error(`Invalid relationship definition "${field}" in class "${type}"`)
59 | }
60 |
61 | export default async function mapObjectToResource(db, obj, fields) {
62 | const type = obj['@class']
63 | const id = obj['@rid']
64 | const classSchema = schemas[type]
65 |
66 | if (!classSchema) {
67 | throw new Error(`Schema for class "${type}" is not defined`)
68 | }
69 |
70 | if (!fields) {
71 | fields = Object.keys(classSchema.attributes || {})
72 | .concat(Object.keys(classSchema.relationships || {}))
73 | }
74 |
75 | const attributes = {}
76 | const relationships = {}
77 |
78 | for (const field of fields) {
79 | if (field === 'id' || field === 'type') {
80 | continue
81 |
82 | } else if (classSchema.attributes[field]) {
83 | attributes[field] = obj[field]
84 |
85 | } else if (classSchema.relationships[field]) {
86 | relationships[field] = {
87 | data: await fetchRelationshipData({
88 | db,
89 | relationship: classSchema.relationships[field],
90 | type,
91 | id,
92 | field
93 | })
94 | }
95 |
96 | } else {
97 | throw new ParametersError(`Class "${type}" haven't field "${field}"`)
98 | }
99 | }
100 |
101 | const resource = {
102 | type,
103 | id: prepareId(id),
104 | attributes,
105 | relationships
106 | }
107 |
108 | return resource
109 | }
110 |
--------------------------------------------------------------------------------
/src/webserver/db/mapResourceToTransaction.js:
--------------------------------------------------------------------------------
1 | export default function mapResourceToTransaction(resource, schema) {
2 | if (!resource) {
3 | return
4 | }
5 |
6 | const className = resource.type
7 |
8 | if (className !== schema.type) {
9 | throw new Error(`Unexpected resource type "${className}", expected "${schema.type}"`)
10 | }
11 |
12 | const id = resource.id && `#${resource.id}`
13 | const resourceAttributes = resource.attributes || {}
14 | const resourceRelationships = resource.relationships || {}
15 | const steps = []
16 |
17 | const attributes = Object.keys(schema.attributes)
18 | .reduce((acc, key) => {
19 | if (resourceAttributes[key]) {
20 | acc[key] = resourceAttributes[key]
21 | }
22 |
23 | return acc
24 | }, {})
25 |
26 | steps.push({
27 | key: 'object',
28 | type: id ? 'updateVertex' : 'createVertex',
29 | class: className,
30 | id,
31 | attributes
32 | })
33 |
34 | Object.keys(schema.relationships)
35 | .filter(key => resourceRelationships[key])
36 | .forEach(key => {
37 | const relationshipSchema = schema.relationships[key]
38 | const data = resourceRelationships[key].data
39 |
40 | if (relationshipSchema.throughEdge && data.length > 0) {
41 | data
42 | .forEach(link => {
43 | if (link.type !== relationshipSchema.class) {
44 | throw new Error(`Unexpected relationships type "${link.type}", expected "${relationshipSchema.class}"`)
45 | }
46 |
47 | const key1 = 'relationshipClean' + steps.length
48 | const key2 = 'relationshipFind' + steps.length
49 | const key3 = 'relationshipEdge' + steps.length
50 |
51 | if (id) {
52 | steps.push({
53 | key: key1,
54 | type: 'removeEdges',
55 | class: relationshipSchema.throughEdge,
56 | from: id
57 | })
58 | }
59 |
60 | steps.push({
61 | key: key2,
62 | type: 'findOne',
63 | class: relationshipSchema.class,
64 | id: `#${link.id}`
65 | })
66 |
67 | steps.push({
68 | key: key3,
69 | type: 'createEdge',
70 | class: relationshipSchema.throughEdge,
71 | from: id ? id : '$object',
72 | to: `\$${key2}`
73 | })
74 | })
75 | }
76 | })
77 |
78 | let returnKey = '$object'
79 |
80 | if (id) {
81 | returnKey = '$result'
82 | steps.push({
83 | key: 'result',
84 | type: 'findOne',
85 | class: schema.type,
86 | id
87 | })
88 | }
89 |
90 | return {steps, returnKey, returnType: 'one'}
91 | }
92 |
--------------------------------------------------------------------------------
/src/webserver/di/app.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import morgan from 'morgan'
3 | import bodyParser from 'body-parser'
4 | import cookieParser from 'cookie-parser'
5 | import orientoDbMiddleware from './orientoDbMiddleware'
6 | import wrapHandler from '../utilities/wrapHandler'
7 | import renderPage from '../utilities/renderPage'
8 | import config from './config'
9 | import handlers from '../handlers'
10 | import wrapPassportMiddleware from './wrapPassportMiddleware'
11 | import cookieAuthMiddleware from './cookieAuthMiddleware'
12 |
13 | // Setup
14 |
15 | const app = express()
16 |
17 | app.use(morgan('combined'))
18 | app.disable('x-powered-by')
19 |
20 | // API
21 |
22 | app.use('/api/*', wrapPassportMiddleware('basic'))
23 | app.use('/api/*', wrapPassportMiddleware('bearer'))
24 | app.use('/api/*', bodyParser.json())
25 | app.use('/api/*', orientoDbMiddleware)
26 |
27 | app.use('/xhr/*', cookieParser())
28 |
29 | handlers
30 | .forEach(({method, route, handler}) => {
31 | app[method](route, wrapHandler(handler))
32 | })
33 |
34 | app.all('/api/*', wrapHandler(() => ({status: 404, body: {data: null}}))) // null-reasonable
35 |
36 | // Front end
37 |
38 | if (config.webserver.publicDir) {
39 | app.use(express.static(config.webserver.publicDir, {
40 | index: false
41 | }))
42 | }
43 |
44 | app.use(cookieParser())
45 | app.use(cookieAuthMiddleware)
46 |
47 | app.get('*', wrapHandler(renderPage))
48 |
49 | export default app
50 |
--------------------------------------------------------------------------------
/src/webserver/di/config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | export default from '../../config'
4 |
--------------------------------------------------------------------------------
/src/webserver/di/cookieAuthMiddleware.js:
--------------------------------------------------------------------------------
1 | import orientoDb from './orientoDb'
2 | import mapObjectToResource from '../db/mapObjectToResource'
3 |
4 | export default async function cookieAuthMiddleware(request, response, next) {
5 | if (!request.cookies.accessToken) {
6 | return next()
7 | }
8 |
9 | try {
10 | const token = await orientoDb
11 | .select()
12 | .from('AccessToken')
13 | .where({hash: request.cookies.accessToken})
14 | .one()
15 |
16 | if (!token) {
17 | return
18 | }
19 |
20 | const tokenResource = await mapObjectToResource(orientoDb, token)
21 |
22 | if (tokenResource.relationships.user.data.length < 1) {
23 | return
24 | }
25 |
26 | const user = await orientoDb
27 | .select()
28 | .from('User')
29 | .where({
30 | '@rid': tokenResource.relationships.user.data[0].id
31 | })
32 | .one()
33 |
34 | if (!user) {
35 | return
36 | }
37 |
38 | const userResource = await mapObjectToResource(orientoDb, user)
39 |
40 | request.tokenResource = tokenResource
41 | request.userResource = userResource
42 | } catch (error) {
43 | return next(error)
44 | }
45 |
46 | next()
47 | }
48 |
--------------------------------------------------------------------------------
/src/webserver/di/orientoDb.js:
--------------------------------------------------------------------------------
1 | import orientoServer from './orientoServer'
2 | import config from './config'
3 |
4 | export default orientoServer.use({
5 | name: config.database.dbname,
6 | username: config.database.dbuser,
7 | password: config.database.dbpassword
8 | })
9 |
--------------------------------------------------------------------------------
/src/webserver/di/orientoDbMiddleware.js:
--------------------------------------------------------------------------------
1 | import orientoDb from './orientoDb'
2 |
3 | export default function orientoDbMiddleware(request, response, next) {
4 | request.db = orientoDb
5 | next()
6 | }
7 |
--------------------------------------------------------------------------------
/src/webserver/di/orientoServer.js:
--------------------------------------------------------------------------------
1 | import oriento from 'oriento'
2 | import config from './config'
3 |
4 | export default oriento({
5 | host: config.database.host,
6 | port: config.database.port,
7 | username: config.database.user,
8 | password: config.database.password
9 | })
10 |
--------------------------------------------------------------------------------
/src/webserver/di/passport.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import {BasicStrategy} from 'passport-http'
3 | import BearerStrategy from 'passport-http-bearer'
4 | import db from './orientoDb'
5 | import bcrypt from 'bcryptjs'
6 |
7 | passport.use(
8 | new BasicStrategy(async (email, password, callback) => {
9 | try {
10 | const user = await db
11 | .select()
12 | .from('User')
13 | .where({email})
14 | .one()
15 |
16 | if (!user) {
17 | return callback(undefined, false)
18 | }
19 |
20 | const validPassword = await new Promise((resolve, reject) => {
21 | bcrypt.compare(password, user.password, (error, result) => {
22 | if (error) {
23 | reject(error)
24 | } else {
25 | resolve(result)
26 | }
27 | })
28 | })
29 |
30 | if (!validPassword) {
31 | return callback(undefined, false)
32 | }
33 |
34 | callback(undefined, user)
35 | } catch (error) {
36 | callback(error)
37 | }
38 | })
39 | )
40 |
41 | passport.use(
42 | new BearerStrategy(async (token, callback) => {
43 | try {
44 | const user = await db
45 | .select('EXPAND(IN("UserToken"))')
46 | .from('AccessToken')
47 | .where({hash: token})
48 | .one()
49 |
50 | if (!user) {
51 | return callback(undefined, false)
52 | }
53 |
54 | callback(undefined, user)
55 |
56 | } catch (error) {
57 | callback(error)
58 | }
59 | })
60 | )
61 |
62 | export default passport
63 |
--------------------------------------------------------------------------------
/src/webserver/di/wrapPassportMiddleware.js:
--------------------------------------------------------------------------------
1 | import passport from './passport'
2 |
3 | export default function wrapPassportMiddleware(name) {
4 | return (request, response, next) => {
5 | const middleware = passport.authenticate(name, {session: false}, (error, user) => {
6 | if (error) {
7 | return next(error)
8 | }
9 |
10 | if (user) {
11 | request.user = user
12 | }
13 |
14 | next()
15 | })
16 |
17 | middleware(request, response, next)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/webserver/handlers/index.js:
--------------------------------------------------------------------------------
1 | import * as users from './users'
2 | import * as tokens from './tokens'
3 | import * as xhr from './xhr'
4 |
5 | export default [
6 | {method: 'post', route: '/xhr/login', handler: xhr.loginHandler},
7 | {method: 'post', route: '/xhr/logout', handler: xhr.logoutHandler},
8 | {method: 'post', route: '/api/v1/tokens/', handler: tokens.postHandler},
9 | {method: 'delete', route: '/api/v1/tokens/', handler: tokens.deleteHandler},
10 | {method: 'get', route: '/api/v1/users/', handler: users.indexHandler},
11 | {method: 'post', route: '/api/v1/users/', handler: users.postHandler},
12 | {method: 'get', route: '/api/v1/users/:id', handler: users.getHandler},
13 | {method: 'patch', route: '/api/v1/users/:id', handler: users.patchHandler},
14 | {method: 'delete', route: '/api/v1/users/:id', handler: users.deleteHandler}
15 | ]
16 |
--------------------------------------------------------------------------------
/src/webserver/handlers/tokens.js:
--------------------------------------------------------------------------------
1 | import applyTransaction from '../db/applyTransaction'
2 | import fulfillQuery from '../db/fulfillQuery'
3 | import getParamsFromRequest from '../utilities/getParamsFromRequest'
4 | import {randomBytes} from 'crypto'
5 |
6 | export async function postHandler(request) {
7 | const {db, user} = request
8 |
9 | if (!user) {
10 | return {
11 | status: 401,
12 | body: {data: null} // null-reasonable
13 | }
14 | }
15 |
16 | const hash = randomBytes(128).toString('hex')
17 |
18 | const token = await applyTransaction(db, {
19 | steps: [
20 | {
21 | key: 'token',
22 | type: 'createVertex',
23 | class: 'AccessToken',
24 | attributes: {
25 | hash
26 | }
27 | },
28 | {
29 | key: 'usertoken',
30 | type: 'createEdge',
31 | class: 'UserToken',
32 | from: user['@rid'],
33 | to: '$token'
34 | }
35 | ],
36 | returnKey: '$token',
37 | returnType: 'one'
38 | })
39 |
40 | const body = await fulfillQuery(
41 | db,
42 | token,
43 | getParamsFromRequest(request)
44 | )
45 |
46 | return {
47 | status: 201,
48 | body
49 | }
50 | }
51 |
52 |
53 | export async function deleteHandler(request) {
54 | const {db, query: {filter: {hash}}} = request
55 |
56 | const deletedCount = await db
57 | .delete('VERTEX', 'AccessToken')
58 | .where({hash})
59 | .limit(1)
60 | .scalar()
61 |
62 | return {
63 | status: deletedCount > 0 ? 200 : 404,
64 | body: {data: null} // null-reasonable
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/webserver/handlers/users.js:
--------------------------------------------------------------------------------
1 | import UserSchema from '../../schemas/User'
2 | import mapResourceToTransaction from '../db/mapResourceToTransaction'
3 | import applyTransaction from '../db/applyTransaction'
4 | import fulfillQuery from '../db/fulfillQuery'
5 | import validateResourceBySchema from '../utilities/validateResourceBySchema'
6 | import getParamsFromRequest from '../utilities/getParamsFromRequest'
7 | import {OP} from 'access-rule'
8 |
9 | export async function indexHandler(request) {
10 | const {db} = request
11 | const currentUser = request.user
12 |
13 | if (!UserSchema.schemaAccessRule(UserSchema, currentUser, OP.GET)) {
14 | return {
15 | status: currentUser ? 403 : 401,
16 | body: {data: null} // null-reasonable
17 | }
18 | }
19 |
20 | const users = (await db
21 | .select()
22 | .from(UserSchema.type)
23 | .all())
24 | .filter(user => UserSchema.resourceAccessRule(user, currentUser, OP.GET))
25 |
26 | const body = await fulfillQuery(
27 | db,
28 | users,
29 | getParamsFromRequest(request)
30 | )
31 |
32 | return {
33 | body
34 | }
35 | }
36 |
37 | export async function getHandler(request) {
38 | const {db, params: {id}} = request
39 | const currentUser = request.user
40 |
41 | if (!UserSchema.schemaAccessRule(UserSchema, currentUser, OP.GET)) {
42 | return {
43 | status: currentUser ? 403 : 401,
44 | body: {data: null} // null-reasonable
45 | }
46 | }
47 |
48 | const user = await db
49 | .select()
50 | .from(UserSchema.type)
51 | .where({'@rid': id})
52 | .one()
53 |
54 | if (!UserSchema.resourceAccessRule(user, currentUser, OP.GET)) {
55 | return {
56 | status: 403,
57 | body: {data: null} // null-reasonable
58 | }
59 | }
60 |
61 | const body = await fulfillQuery(
62 | db,
63 | user,
64 | getParamsFromRequest(request)
65 | )
66 |
67 | return {
68 | status: body.data ? 200 : 404,
69 | body
70 | }
71 | }
72 |
73 | export async function postHandler(request) {
74 | const {db, body: {data}} = request
75 | const currentUser = request.user
76 |
77 | if (!UserSchema.schemaAccessRule(UserSchema, currentUser, OP.POST)) {
78 | return {
79 | status: currentUser ? 403 : 401,
80 | body: {data: null} // null-reasonable
81 | }
82 | }
83 |
84 | if (!data) {
85 | return {
86 | status: 400,
87 | body: {errors: [{
88 | title: 'Request must include a single resource object as primary data'
89 | }]}
90 | }
91 | }
92 |
93 | if (data.id) {
94 | return {
95 | status: 400,
96 | body: {errors: [{
97 | title: 'Client-generated id doesn\'t supported'
98 | }]}
99 | }
100 | }
101 |
102 | const errors = validateResourceBySchema(data, UserSchema, true)
103 |
104 | if (errors.length > 0) {
105 | return {
106 | status: 400,
107 | body: {errors}
108 | }
109 | }
110 |
111 | const normalizedData = UserSchema.normalize
112 | ? UserSchema.normalize(data)
113 | : data
114 | const transaction = mapResourceToTransaction(normalizedData, UserSchema)
115 | const user = await applyTransaction(db, transaction)
116 |
117 | const body = await fulfillQuery(
118 | db,
119 | user,
120 | getParamsFromRequest(request)
121 | )
122 |
123 | return {
124 | status: 201,
125 | body
126 | }
127 | }
128 |
129 | export async function deleteHandler(request) {
130 | const {db, params: {id}} = request
131 | const currentUser = request.user
132 |
133 | if (!UserSchema.schemaAccessRule(UserSchema, currentUser, OP.DELETE)) {
134 | return {
135 | status: currentUser ? 403 : 401,
136 | body: {data: null} // null-reasonable
137 | }
138 | }
139 |
140 | const user = await db
141 | .select()
142 | .from(UserSchema.type)
143 | .where({'@rid': id})
144 | .one()
145 |
146 | if (!user) {
147 | return {
148 | status: 404,
149 | body: {data: null} // null-reasonable
150 | }
151 | }
152 |
153 | if (!UserSchema.resourceAccessRule(user, currentUser, OP.DELETE)) {
154 | return {
155 | status: 403,
156 | body: {data: null} // null-reasonable
157 | }
158 | }
159 |
160 | await db
161 | .delete('VERTEX', UserSchema.type)
162 | .where({'@rid': id})
163 | .limit(1)
164 | .scalar()
165 |
166 | return {
167 | status: 200,
168 | body: {data: null} // null-reasonable
169 | }
170 | }
171 |
172 | export async function patchHandler(request) {
173 | const {db, params: {id}, body: {data}} = request
174 | const currentUser = request.user
175 |
176 | if (!UserSchema.schemaAccessRule(UserSchema, currentUser, OP.PATCH)) {
177 | return {
178 | status: currentUser ? 403 : 401,
179 | body: {data: null} // null-reasonable
180 | }
181 | }
182 |
183 | const user = await db
184 | .select()
185 | .from(UserSchema.type)
186 | .where({'@rid': id})
187 | .one()
188 |
189 | if (!user) {
190 | return {
191 | status: 404,
192 | body: {data: null} // null-reasonable
193 | }
194 | }
195 |
196 | if (!UserSchema.resourceAccessRule(user, currentUser, OP.PATCH)) {
197 | return {
198 | status: 403,
199 | body: {data: null} // null-reasonable
200 | }
201 | }
202 |
203 | if (!data) {
204 | return {
205 | status: 400,
206 | body: {errors: [{
207 | title: 'Request must include a single resource object as primary data'
208 | }]}
209 | }
210 | }
211 |
212 | if (data.id !== id) {
213 | return {
214 | status: 400,
215 | body: {errors: [{
216 | title: 'Resource id can not be changed'
217 | }]}
218 | }
219 | }
220 |
221 | const errors = validateResourceBySchema(data, UserSchema)
222 |
223 | if (errors.length > 0) {
224 | return {
225 | status: 400,
226 | body: {errors}
227 | }
228 | }
229 |
230 | const normalizedData = UserSchema.normalize
231 | ? UserSchema.normalize(data)
232 | : data
233 | const transaction = mapResourceToTransaction(normalizedData, UserSchema)
234 | const updatedUser = await applyTransaction(db, transaction)
235 |
236 | const body = await fulfillQuery(
237 | db,
238 | updatedUser,
239 | getParamsFromRequest(request)
240 | )
241 |
242 | return {
243 | status: 200,
244 | body
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/webserver/handlers/xhr.js:
--------------------------------------------------------------------------------
1 | import apiRequest from '../../utilities/apiRequest'
2 | import getParamsFromRequest from '../utilities/getParamsFromRequest'
3 |
4 | export async function loginHandler(request) {
5 | const {fields, include} = getParamsFromRequest(request)
6 |
7 | const response = await apiRequest('/api/v1/tokens/', {
8 | method: 'POST',
9 | fields,
10 | include,
11 | headers: {
12 | 'Authorization': request.headers.authorization
13 | }
14 | })
15 |
16 | return {
17 | cookies: [
18 | {
19 | name: 'accessToken',
20 | value: response.data.attributes.hash,
21 | httpOnly: true
22 | }
23 | ],
24 | body: response
25 | }
26 | }
27 |
28 | export async function logoutHandler(request) {
29 | const token = request.cookies.accessToken
30 |
31 | const response = await apiRequest(`/api/v1/tokens/?filter[hash]=${token}`, {
32 | method: 'DELETE',
33 | headers: {
34 | 'Authorization': `Bearer ${token}`
35 | }
36 | })
37 |
38 | return {
39 | body: response
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/webserver/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import './polyfills'
4 | export default from './di/app'
5 |
--------------------------------------------------------------------------------
/src/webserver/polyfills.js:
--------------------------------------------------------------------------------
1 | import '../utilities/patchReact'
2 | import fetch from 'node-fetch'
3 |
4 | global.fetch = fetch
5 |
--------------------------------------------------------------------------------
/src/webserver/utilities/appTemplate.js:
--------------------------------------------------------------------------------
1 | export default function appTemplate(title, content, state) {
2 | return `
3 |
4 |
5 |
6 | ${title}
7 |
8 |
9 | ${content}
10 |
11 |
21 |
22 |
23 | `
24 | }
25 |
--------------------------------------------------------------------------------
/src/webserver/utilities/getParamsFromRequest.js:
--------------------------------------------------------------------------------
1 | import {mapObj} from 'ramda'
2 |
3 | export default function getParamsFromRequest(request) {
4 | const fields = typeof request.query.fields === 'object'
5 | ? mapObj(fs => fs.split(',').filter(f => f.trim().length > 0), request.query.fields)
6 | : {}
7 |
8 | const include = request.query.include
9 | ? request.query.include.split(',')
10 | : []
11 |
12 | return {fields, include}
13 | }
14 |
--------------------------------------------------------------------------------
/src/webserver/utilities/renderPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import createDispatcher from '../../utilities/createDispatcher'
3 | import appTemplate from './appTemplate'
4 | import {setToken} from '../../flux/token/tokenActions'
5 | import {runTransition} from '../../flux/router/routerActions'
6 | import {mergeResources} from '../../flux/resources/resourcesActions'
7 | import Application from '../../components/Application'
8 |
9 | export default async function renderPage(request) {
10 | const {tokenResource, userResource} = request
11 |
12 | const dispatcher = createDispatcher()
13 |
14 | if (tokenResource && userResource) {
15 | await dispatcher.dispatch(setToken(tokenResource.attributes.hash))
16 | await dispatcher.dispatch(mergeResources([tokenResource, userResource]))
17 | }
18 |
19 | const {title, status} = await dispatcher.dispatch(runTransition(request.originalUrl))
20 |
21 | if (status === 401) {
22 | return {
23 | status: 302,
24 | location: '/'
25 | }
26 | }
27 |
28 | const state = dispatcher.getState()
29 |
30 | const pageContent = React.renderToString(
31 |
32 | )
33 |
34 | return {
35 | headers: {
36 | 'Cache-Control': 'no-cache, private, max-age=86400',
37 | 'Vary': 'Cookie'
38 | },
39 | status,
40 | body: appTemplate(title, pageContent, state)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/webserver/utilities/validateResourceBySchema.js:
--------------------------------------------------------------------------------
1 | export default function validateResourceBySchema(resource, schema, force = false) {
2 | const resourceAttributes = resource && resource.attributes || {}
3 | const resourceRelationships = resource && resource.relationships || {}
4 | const schemaAttributes = schema.attributes || {}
5 | const schemaRelationships = schema.relationships || {}
6 |
7 | const attributeErrors = Object.keys(schemaAttributes)
8 | .reduce((acc, key) => {
9 | const constraint = schemaAttributes[key]
10 | const value = resourceAttributes[key]
11 | const validationNeeded = key in resourceAttributes || force
12 |
13 | if (validationNeeded) {
14 | const constraintResult = constraint(value)
15 |
16 | if (!constraintResult.valid) {
17 | acc.push({
18 | status: 400,
19 | title: constraintResult.message,
20 | source: {
21 | pointer: `/data/attributes/${key}`
22 | },
23 | meta: {
24 | constraintResult
25 | }
26 | })
27 | }
28 | }
29 |
30 | return acc
31 | }, [])
32 |
33 | const relationshipErrors = Object.keys(schemaRelationships)
34 | .reduce((acc, key) => {
35 | const relationshipSchema = schemaRelationships[key]
36 | const relationship = resourceRelationships[key]
37 |
38 | const validateRelationship = ({type, id}, index) => {
39 | if (type !== relationshipSchema.class) {
40 | acc.push({
41 | status: 400,
42 | title: `Relationship type doesn't match schema`,
43 | source: {
44 | pointer: `/data/relationships/${key}`
45 | },
46 | meta: {
47 | actual: type,
48 | expected: relationshipSchema.class,
49 | index
50 | }
51 | })
52 | }
53 | }
54 |
55 | if (relationship) {
56 | const {data} = relationship
57 |
58 | if (Array.isArray(data)) {
59 | data.map(validateRelationship)
60 | } else if (data) {
61 | validateRelationship(data)
62 | }
63 | }
64 |
65 | return acc
66 | }, [])
67 |
68 | return [...attributeErrors, ...relationshipErrors]
69 | }
70 |
--------------------------------------------------------------------------------
/src/webserver/utilities/wrapHandler.js:
--------------------------------------------------------------------------------
1 | import {ParametersError} from '../db/errors'
2 |
3 | function getErrorMetaInformation(error) {
4 | const errorMeta = {
5 | status: 500,
6 | title: error.message,
7 | detail: error.stack
8 | }
9 |
10 | if (error.responseStatus) {
11 | errorMeta.status = error.responseStatus
12 |
13 | if (error.responseStatus < 500) {
14 | delete errorMeta.detail
15 | }
16 | }
17 |
18 | if (error instanceof ParametersError) {
19 | errorMeta.status = 400
20 | }
21 |
22 | if (error.type === 'com.orientechnologies.orient.core.storage.ORecordDuplicatedException') {
23 | const matches = /key \'(.*)\' in index \'(.*)\' previously/.exec(error.message)
24 |
25 | if (matches) {
26 | const value = matches[1]
27 | const dbIndex = matches[2].split('.')
28 | const className = dbIndex[0]
29 | const fields = dbIndex[1].split('_')
30 |
31 | delete errorMeta.detail
32 |
33 | errorMeta.status = 409
34 | errorMeta.title = `Found duplicated key "${value}" in index "${matches[2]}"`
35 | errorMeta.meta = {value, dbIndex, className, fields}
36 |
37 | if (fields.length === 1) {
38 | errorMeta.source = {
39 | pointer: `/data/attributes/${fields[0]}`
40 | }
41 | }
42 | }
43 | }
44 |
45 | return errorMeta
46 | }
47 |
48 | export default function wrapHandler(handler) {
49 | return async (request, response) => {
50 | const isApiRequest = /^\/api\//.test(request.originalUrl)
51 | const isAjaxRequest = /^\/xhr\//.test(request.originalUrl)
52 |
53 | let contentType
54 | let status = 200
55 | let body
56 |
57 | try {
58 | const responseMeta = await handler(request)
59 |
60 | if (responseMeta.location) {
61 | response.location(responseMeta.location)
62 | }
63 |
64 | if (responseMeta.headers) {
65 | Object.keys(responseMeta.headers)
66 | .forEach(key => {
67 | response.set(key, responseMeta.headers[key])
68 | })
69 | }
70 |
71 | if (responseMeta.cookies) {
72 | responseMeta.cookies
73 | .forEach(({name, value, ...options}) => {
74 | response.cookie(name, value, options)
75 | })
76 | }
77 |
78 | if (responseMeta.contentType) {
79 | contentType = responseMeta.contentType
80 | }
81 |
82 | if (responseMeta.status) {
83 | status = responseMeta.status
84 | }
85 |
86 | if (responseMeta.body) {
87 | body = responseMeta.body
88 | }
89 | } catch (error) {
90 | const errorMeta = getErrorMetaInformation(error)
91 |
92 | status = errorMeta.status
93 |
94 | if (isApiRequest || isAjaxRequest) {
95 | body = {errors: [errorMeta]}
96 | } else {
97 | contentType = 'text/plain'
98 | body = errorMeta.detail || errorMeta.title
99 | }
100 | }
101 |
102 | if (!contentType && isAjaxRequest) {
103 | contentType = 'application/json'
104 | }
105 |
106 | if (!contentType && isApiRequest) {
107 | contentType = 'application/vnd.api+json'
108 | }
109 |
110 | if (contentType) {
111 | response.header('Content-Type', contentType)
112 | }
113 |
114 | if (typeof body === 'number') {
115 | body = String(body)
116 | }
117 |
118 | response.status(status)
119 | response.send(body)
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "chai": true,
7 | "assert": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/api/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../src/api/index' // eslint-disable-line
2 |
3 | describe('api/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/api/usersPageApiRequestTest.js:
--------------------------------------------------------------------------------
1 | import usersPageApiRequest from '../../src/api/usersPageApiRequest' // eslint-disable-line
2 |
3 | describe('api/usersPageApiRequest', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/AboutPage/AboutPageTest.js:
--------------------------------------------------------------------------------
1 | import AboutPage from '../../../src/components/AboutPage/AboutPage' // eslint-disable-line
2 |
3 | describe('components/AboutPage/AboutPage', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/AboutPage/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/components/AboutPage/index' // eslint-disable-line
2 |
3 | describe('components/AboutPage/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/Application/ApplicationTest.js:
--------------------------------------------------------------------------------
1 | import Application from '../../../src/components/Application/Application' // eslint-disable-line
2 |
3 | describe('components/Application/Application', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/Application/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/components/Application/index' // eslint-disable-line
2 |
3 | describe('components/Application/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/ErrorPage/ErrorPageTest.js:
--------------------------------------------------------------------------------
1 | import ErrorPage from '../../../src/components/ErrorPage/ErrorPage' // eslint-disable-line
2 |
3 | describe('components/ErrorPage/ErrorPage', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/ErrorPage/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/components/ErrorPage/index' // eslint-disable-line
2 |
3 | describe('components/ErrorPage/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/HomePage/HomePageTest.js:
--------------------------------------------------------------------------------
1 | import HomePage from '../../../src/components/HomePage/HomePage' // eslint-disable-line
2 |
3 | describe('components/HomePage/HomePage', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/HomePage/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/components/HomePage/index' // eslint-disable-line
2 |
3 | describe('components/HomePage/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/Link/LinkTest.js:
--------------------------------------------------------------------------------
1 | import Link from '../../../src/components/Link/Link' // eslint-disable-line
2 |
3 | describe('components/Link/Link', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/Link/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/components/Link/index' // eslint-disable-line
2 |
3 | describe('components/Link/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/NotFoundPage/NotFoundPageTest.js:
--------------------------------------------------------------------------------
1 | import NotFoundPage from '../../../src/components/NotFoundPage/NotFoundPage' // eslint-disable-line
2 |
3 | describe('components/NotFoundPage/NotFoundPage', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/NotFoundPage/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/components/NotFoundPage/index' // eslint-disable-line
2 |
3 | describe('components/NotFoundPage/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/SignIn/SignInTest.js:
--------------------------------------------------------------------------------
1 | import SignIn from '../../../src/components/SignIn/SignIn' // eslint-disable-line
2 |
3 | describe('components/SignIn/SignIn', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/SignIn/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/components/SignIn/index' // eslint-disable-line
2 |
3 | describe('components/SignIn/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/UsersPage/ItemTest.js:
--------------------------------------------------------------------------------
1 | import Item from '../../../src/components/UsersPage/Item' // eslint-disable-line
2 |
3 | describe('components/UsersPage/Item', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/UsersPage/OrderTest.js:
--------------------------------------------------------------------------------
1 | import Order from '../../../src/components/UsersPage/Order' // eslint-disable-line
2 |
3 | describe('components/UsersPage/Order', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/UsersPage/PositionTest.js:
--------------------------------------------------------------------------------
1 | import Position from '../../../src/components/UsersPage/Position' // eslint-disable-line
2 |
3 | describe('components/UsersPage/Position', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/UsersPage/UserTest.js:
--------------------------------------------------------------------------------
1 | import User from '../../../src/components/UsersPage/User' // eslint-disable-line
2 |
3 | describe('components/UsersPage/User', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/UsersPage/UsersPageTest.js:
--------------------------------------------------------------------------------
1 | import UsersPage from '../../../src/components/UsersPage/UsersPage' // eslint-disable-line
2 |
3 | describe('components/UsersPage/UsersPage', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/UsersPage/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/components/UsersPage/index' // eslint-disable-line
2 |
3 | describe('components/UsersPage/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/components/pagesTest.js:
--------------------------------------------------------------------------------
1 | import * as pages from '../../src/components/pages'
2 |
3 | describe('components/pages', () => {
4 | it('should contain only pages', () => {
5 | Object.keys(pages).forEach(key => {
6 | assert.match(key, /Page$/)
7 | })
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/test/config/routingTest.js:
--------------------------------------------------------------------------------
1 | import routing from '../../src/config/routing' // eslint-disable-line
2 |
3 | describe('config/routing', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/resources/resourcesActionsTest.js:
--------------------------------------------------------------------------------
1 | import resourcesActions from '../../../src/flux/resources/resourcesActions' // eslint-disable-line
2 |
3 | describe('flux/resources/resourcesActions', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/resources/resourcesConstantsTest.js:
--------------------------------------------------------------------------------
1 | import resourcesConstants from '../../../src/flux/resources/resourcesConstants' // eslint-disable-line
2 |
3 | describe('flux/resources/resourcesConstants', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/resources/resourcesStoreTest.js:
--------------------------------------------------------------------------------
1 | import resourcesStore from '../../../src/flux/resources/resourcesStore' // eslint-disable-line
2 |
3 | describe('flux/resources/resourcesStore', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/router/routerActionsTest.js:
--------------------------------------------------------------------------------
1 | import routerActions from '../../../src/flux/router/routerActions' // eslint-disable-line
2 |
3 | describe('flux/router/routerActions', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/router/routerConstantsTest.js:
--------------------------------------------------------------------------------
1 | import routerConstants from '../../../src/flux/router/routerConstants' // eslint-disable-line
2 |
3 | describe('flux/router/routerConstants', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/router/routerStoreTest.js:
--------------------------------------------------------------------------------
1 | import routerStore from '../../../src/flux/router/routerStore' // eslint-disable-line
2 |
3 | describe('flux/router/routerStore', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/signIn/signInActionsTest.js:
--------------------------------------------------------------------------------
1 | import signInActions from '../../../src/flux/signIn/signInActions' // eslint-disable-line
2 |
3 | describe('flux/signIn/signInActions', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/signIn/signInConstantsTest.js:
--------------------------------------------------------------------------------
1 | import signInConstants from '../../../src/flux/signIn/signInConstants' // eslint-disable-line
2 |
3 | describe('flux/signIn/signInConstants', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/signIn/signInStoreTest.js:
--------------------------------------------------------------------------------
1 | import signInStore from '../../../src/flux/signIn/signInStore' // eslint-disable-line
2 |
3 | describe('flux/signIn/signInStore', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/signIn/signInValidatorTest.js:
--------------------------------------------------------------------------------
1 | import signInValidator from '../../../src/flux/signIn/signInValidator' // eslint-disable-line
2 |
3 | describe('flux/signIn/signInValidator', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/storesTest.js:
--------------------------------------------------------------------------------
1 | import stores from '../../src/flux/stores' // eslint-disable-line
2 |
3 | describe('flux/stores', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/token/tokenActionsTest.js:
--------------------------------------------------------------------------------
1 | import tokenActions from '../../../src/flux/token/tokenActions' // eslint-disable-line
2 |
3 | describe('flux/token/tokenActions', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/token/tokenConstantsTest.js:
--------------------------------------------------------------------------------
1 | import tokenConstants from '../../../src/flux/token/tokenConstants' // eslint-disable-line
2 |
3 | describe('flux/token/tokenConstants', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/token/tokenStoreTest.js:
--------------------------------------------------------------------------------
1 | import tokenStore from '../../../src/flux/token/tokenStore' // eslint-disable-line
2 |
3 | describe('flux/token/tokenStore', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/transition/createFetchTransitionTest.js:
--------------------------------------------------------------------------------
1 | import createFetchTransition from '../../../src/flux/transition/createFetchTransition' // eslint-disable-line
2 |
3 | describe('flux/transition/createFetchTransition', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/transition/createStaticTransitionTest.js:
--------------------------------------------------------------------------------
1 | import createStaticTransition from '../../../src/flux/transition/createStaticTransition' // eslint-disable-line
2 |
3 | describe('flux/transition/createStaticTransition', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/flux/transition/transitionActionsTest.js:
--------------------------------------------------------------------------------
1 | import transitionActions from '../../../src/flux/transition/transitionActions' // eslint-disable-line
2 |
3 | describe('flux/transition/transitionActions', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/schemas/AccessTokenTest.js:
--------------------------------------------------------------------------------
1 | import AccessToken from '../../src/schemas/AccessToken' // eslint-disable-line
2 |
3 | describe('schemas/AccessToken', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/schemas/ItemTest.js:
--------------------------------------------------------------------------------
1 | import Item from '../../src/schemas/Item' // eslint-disable-line
2 |
3 | describe('schemas/Item', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/schemas/OrderPositionTest.js:
--------------------------------------------------------------------------------
1 | import OrderPosition from '../../src/schemas/OrderPosition' // eslint-disable-line
2 |
3 | describe('schemas/OrderPosition', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/schemas/OrderTest.js:
--------------------------------------------------------------------------------
1 | import Order from '../../src/schemas/Order' // eslint-disable-line
2 |
3 | describe('schemas/Order', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/schemas/UserTest.js:
--------------------------------------------------------------------------------
1 | import User from '../../src/schemas/User' // eslint-disable-line
2 |
3 | describe('schemas/User', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/schemas/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../src/schemas/index' // eslint-disable-line
2 |
3 | describe('schemas/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/apiRequestTest.js:
--------------------------------------------------------------------------------
1 | import apiRequest from '../../src/utilities/apiRequest' // eslint-disable-line
2 |
3 | describe('utilities/apiRequest', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/bindActionTest.js:
--------------------------------------------------------------------------------
1 | import bindAction from '../../src/utilities/bindAction' // eslint-disable-line
2 |
3 | describe('utilities/bindAction', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/collectResourcesTest.js:
--------------------------------------------------------------------------------
1 | import collectResources from '../../src/utilities/collectResources' // eslint-disable-line
2 |
3 | describe('utilities/collectResources', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/createDispatcherTest.js:
--------------------------------------------------------------------------------
1 | import createDispatcher from '../../src/utilities/createDispatcher' // eslint-disable-line
2 |
3 | describe('utilities/createDispatcher', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/createStoreTest.js:
--------------------------------------------------------------------------------
1 | import createStore from '../../src/utilities/createStore' // eslint-disable-line
2 |
3 | describe('utilities/createStore', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/getCurrentUserTest.js:
--------------------------------------------------------------------------------
1 | import getCurrentUser from '../../src/utilities/getCurrentUser' // eslint-disable-line
2 |
3 | describe('utilities/getCurrentUser', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/patchReactTest.js:
--------------------------------------------------------------------------------
1 | import patchReact from '../../src/utilities/patchReact' // eslint-disable-line
2 |
3 | describe('utilities/patchReact', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/preventEventTest.js:
--------------------------------------------------------------------------------
1 | import preventEvent from '../../src/utilities/preventEvent' // eslint-disable-line
2 |
3 | describe('utilities/preventEvent', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/utilities/progressIndicatorTest.js:
--------------------------------------------------------------------------------
1 | import progressIndicator from '../../src/utilities/progressIndicator' // eslint-disable-line
2 |
3 | describe('utilities/progressIndicator', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/db/applyTransactionTest.js:
--------------------------------------------------------------------------------
1 | import applyTransaction from '../../../src/webserver/db/applyTransaction' // eslint-disable-line
2 |
3 | describe('webserver/db/applyTransaction', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/db/errorsTest.js:
--------------------------------------------------------------------------------
1 | import errors from '../../../src/webserver/db/errors' // eslint-disable-line
2 |
3 | describe('webserver/db/errors', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/db/fulfillQueryTest.js:
--------------------------------------------------------------------------------
1 | import fulfillQuery from '../../../src/webserver/db/fulfillQuery' // eslint-disable-line
2 |
3 | describe('webserver/db/fulfillQuery', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/db/mapObjectToResourceTest.js:
--------------------------------------------------------------------------------
1 | import mapObjectToResource from '../../../src/webserver/db/mapObjectToResource' // eslint-disable-line
2 |
3 | describe('webserver/db/mapObjectToResource', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/db/mapResourceToTransactionTest.js:
--------------------------------------------------------------------------------
1 | import mapResourceToTransaction from '../../../src/webserver/db/mapResourceToTransaction' // eslint-disable-line
2 |
3 | describe('webserver/db/mapResourceToTransaction', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/handlers/indexTest.js:
--------------------------------------------------------------------------------
1 | import index from '../../../src/webserver/handlers/index' // eslint-disable-line
2 |
3 | describe('webserver/handlers/index', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/handlers/tokensTest.js:
--------------------------------------------------------------------------------
1 | import tokens from '../../../src/webserver/handlers/tokens' // eslint-disable-line
2 |
3 | describe('webserver/handlers/tokens', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/handlers/usersTest.js:
--------------------------------------------------------------------------------
1 | import users from '../../../src/webserver/handlers/users' // eslint-disable-line
2 |
3 | describe('webserver/handlers/users', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/handlers/xhrTest.js:
--------------------------------------------------------------------------------
1 | import xhr from '../../../src/webserver/handlers/xhr' // eslint-disable-line
2 |
3 | describe('webserver/handlers/xhr', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/utilities/appTemplateTest.js:
--------------------------------------------------------------------------------
1 | import appTemplate from '../../../src/webserver/utilities/appTemplate' // eslint-disable-line
2 |
3 | describe('webserver/utilities/appTemplate', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/utilities/getParamsFromRequestTest.js:
--------------------------------------------------------------------------------
1 | import getParamsFromRequest from '../../../src/webserver/utilities/getParamsFromRequest' // eslint-disable-line
2 |
3 | describe('webserver/utilities/getParamsFromRequest', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/utilities/renderPageTest.js:
--------------------------------------------------------------------------------
1 | import renderPage from '../../../src/webserver/utilities/renderPage' // eslint-disable-line
2 |
3 | describe('webserver/utilities/renderPage', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/utilities/validateResourceBySchemaTest.js:
--------------------------------------------------------------------------------
1 | import validateResourceBySchema from '../../../src/webserver/utilities/validateResourceBySchema' // eslint-disable-line
2 |
3 | describe('webserver/utilities/validateResourceBySchema', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/test/webserver/utilities/wrapHandlerTest.js:
--------------------------------------------------------------------------------
1 | import wrapHandler from '../../../src/webserver/utilities/wrapHandler' // eslint-disable-line
2 |
3 | describe('webserver/utilities/wrapHandler', () => {
4 | it('should work')
5 | })
6 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | require('babel-core/register')
2 |
3 | module.exports = [
4 | require('./build/webpack/frontend'),
5 | require('./build/webpack/webserver'),
6 | require('./build/webpack/migrate'),
7 | require('./build/webpack/fixtures')
8 | ]
9 |
--------------------------------------------------------------------------------