├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── api ├── database.js └── routes.js ├── client └── src │ ├── components │ ├── app.js │ ├── endpoint-controls.js │ ├── footer.js │ └── navigator.js │ ├── html.js │ ├── index.js │ ├── public │ ├── css │ │ └── main.css │ ├── favicon.png │ ├── index.html │ └── js │ │ └── drift.js │ └── services │ └── appService.js ├── logo.png ├── package-lock.json ├── package.json ├── server.js ├── webpack.config.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | "transform-react-jsx", 5 | "transform-class-properties" 6 | ] 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Eliran Pe'er 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 |

7 |

8 | 9 | 10 | 11 |

12 |

13 | Start storing your data in the cloud in 2 seconds 14 |

15 | 16 | ## jsonstore.io 17 | jsonstore.io offers a free, secured and JSON based cloud datastore for small projects. 18 | Just enter https://www.jsonstore.io/, copy the URL and start sending HTTP requests to communicate with your datastore. 19 | POST requests will save data, PUT requests modify data, DELETE requests delete data and GET requests retrieves data. 20 | 21 | ## Examples 22 | Make sure to replace the URL in the examples to your own endpoint, that can be found at https://www.jsonstore.io/. 23 | ### CURL 24 | #### POST 25 | The following command will create a user in `/users/1`: 26 | ```shell 27 | curl -XPOST -H "Content-type: application/json" -d '{ 28 | "name": "jon.snow", 29 | "age": 31 30 | }' 'https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users/1' 31 | ``` 32 | 33 | #### GET 34 | The following command will retrieve the user we created earlier: 35 | ```shell 36 | curl -XGET 'https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users/1' 37 | ``` 38 | 39 | ##### Querying and Sorting 40 | To query the data, use the query parameters `orderKey`, `filterValue` and `valueType` 41 | 42 | - `orderKey`: name of the key in child. For example, to order by `age` use, 43 | ```shell 44 | curl -XGET 'https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users?orderKey=age' 45 | ``` 46 | 47 | > if `orderKey` in not present in child, that child will come in order before children with `orderKey` 48 | 49 | - `filterValue`: value of key (given using `orderKey`), to filter the children by. For example, to get the users with `age`=`20` 50 | ```shell 51 | curl -XGET 'https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users?orderKey=age&filterValue=20' 52 | ``` 53 | > `filterValue` should be used in conjugation with `orderBy`. 54 | 55 | - `valueType`: enforcing type of `filterValue`. Type of `filterValue` is guessed by `jsonstore`. If you want to enforce a type, send `string`, `number` or `boolean` as `valueType`. For eg, 56 | ```shell 57 | curl -XGET 'https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users?orderKey=age&filterValue=20&valueType=number' 58 | ``` 59 | 60 | #### PUT 61 | The following command will change the age of the user to `32`: 62 | ```shell 63 | curl -XPUT -H "Content-type: application/json" -d '32' 'https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users/1/age' 64 | ``` 65 | 66 | #### DELETE 67 | The following command will delete the user: 68 | ```shell 69 | curl -XDELETE 'https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users/1' 70 | ``` 71 | 72 | ### JavaScript 73 | #### POST 74 | ```js 75 | fetch('https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users/1', { 76 | headers: { 77 | 'Content-type': 'application/json' 78 | }, 79 | method: 'POST', 80 | body: { name: 'jon snow', age: 31 }, 81 | }); 82 | ``` 83 | 84 | #### GET 85 | ```js 86 | const user = await fetch('https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users/1') 87 | .then(function(response) { 88 | return response.json(); 89 | }) 90 | ``` 91 | 92 | #### PUT 93 | ```js 94 | fetch('https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users/1/age', { 95 | headers: { 96 | 'Content-type': 'application/json' 97 | }, 98 | method: 'PUT', 99 | body: 32, 100 | }); 101 | ``` 102 | 103 | #### DELETE 104 | ```js 105 | fetch('https://www.jsonstore.io/cf024bb815a93131ce9fed91b1f9dafa43a3c557e9be66e66fd76df5c64f10fe/users/1', { 106 | method: 'DELETE', 107 | }); 108 | ``` 109 | 110 | ### Contribution 111 | Any type of feedback, pull request or issue is welcome. 112 | -------------------------------------------------------------------------------- /api/database.js: -------------------------------------------------------------------------------- 1 | const firebase = require('firebase'); 2 | 3 | function guessType(value) { 4 | const number = /^-?\d*\.?\d*$/; 5 | const boolean = /(true|false)/; 6 | 7 | if (number.test(value)) { 8 | return Number; 9 | } 10 | 11 | if (boolean.test(value)) { 12 | return JSON.parse; 13 | } 14 | 15 | return ((v) => v); 16 | } 17 | 18 | function convert(value, type) { 19 | const typemap = { 20 | 'number': Number, 21 | 'boolean': Boolean, 22 | 'string': String, 23 | }; 24 | const converter = typemap[type] || guessType(value); 25 | return converter(value); 26 | } 27 | 28 | 29 | if (!process.env.FIREBASE_CONFIG) { 30 | module.exports = { 31 | get: () => (console.log(arguments), Promise.resolve()), 32 | post: () => (console.log(arguments), Promise.resolve()), 33 | put: () => (console.log(arguments), Promise.resolve()), 34 | delete: () => (console.log(arguments), Promise.resolve()), 35 | } 36 | } else { 37 | const config = JSON.parse(process.env.FIREBASE_CONFIG); 38 | firebase.initializeApp(config); 39 | 40 | module.exports = { 41 | get: (key, orderKey, filterValue, valueType) => { 42 | let ref = firebase.database().ref(key); 43 | 44 | if (orderKey) { 45 | ref = ref.orderByChild(orderKey); 46 | if (filterValue !== undefined) { 47 | ref = ref.equalTo(convert(filterValue, valueType)); 48 | } 49 | } 50 | return ref 51 | .once('value') 52 | .then(snapshot => { 53 | if (!orderKey) { 54 | return snapshot.val(); 55 | } 56 | 57 | const data = []; 58 | // snapshot.val() forgets any ordering info 59 | snapshot.forEach(item => { 60 | if (item.val()) { 61 | data.push(item.val()); 62 | } 63 | }); 64 | return data; 65 | }); 66 | }, 67 | 68 | post: (key, data) => 69 | firebase 70 | .database() 71 | .ref(key) 72 | .set(data), 73 | 74 | put: (key, data) => 75 | firebase 76 | .database() 77 | .ref() 78 | .update({ 79 | [key]: data, 80 | }), 81 | 82 | delete: key => 83 | firebase 84 | .database() 85 | .ref(key) 86 | .remove(), 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /api/routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const crypto = require('crypto'); 3 | const database = require('./database'); 4 | const { URL } = require('url'); 5 | 6 | function checkContentType(req, res, next) { 7 | if (!req.is('application/json')) { 8 | return res.status(400).send({ ok: false, error: "Bad request" }); 9 | } 10 | 11 | next(); 12 | } 13 | 14 | const router = express.Router(); 15 | 16 | router.get('/get-token', (req, res) => { 17 | const seed = crypto.randomBytes(64); 18 | const hash = crypto.createHash('sha256').update(seed).digest('hex'); 19 | return res.send({ token: hash }); 20 | }); 21 | 22 | router.get(/^\/[0-9a-z]{64}/, (req, res) => { 23 | const { orderKey, filterValue, valueType } = req.query; 24 | 25 | database 26 | .get(req.path, orderKey, filterValue, valueType) 27 | .then(result => res.status(200).send({ result, ok: true })) 28 | .catch(() => res.status(500).send({ ok: false })) 29 | }); 30 | 31 | router.post(/^\/[0-9a-z]{64}/, checkContentType, (req, res) => 32 | database 33 | .post(req.path, req.body) 34 | .then(() => res.status(201).send({ ok: true })) 35 | .catch(() => res.status(500).send({ ok: false })) 36 | ) 37 | 38 | router.put(/^\/[0-9a-z]{64}/, checkContentType, (req, res) => 39 | database 40 | .put(req.path, req.body) 41 | .then(() => res.status(200).send({ ok: true })) 42 | .catch(() => res.status(500).send({ ok: false })) 43 | ); 44 | 45 | router.delete(/^\/[0-9a-z]{64}/, (req, res) => 46 | database 47 | .delete(req.path, req.body) 48 | .then(() => res.status(200).send({ ok: true })) 49 | .catch(() => res.status(500).send({ ok: false })) 50 | ); 51 | 52 | module.exports = router; 53 | -------------------------------------------------------------------------------- /client/src/components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Navigator from './navigator'; 4 | import EndpointControls from './endpoint-controls'; 5 | import Footer from './footer'; 6 | 7 | const App = () => 8 |
9 | 10 | 11 |
12 |
13 | 14 | export default App; -------------------------------------------------------------------------------- /client/src/components/endpoint-controls.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppService from './../services/appService'; 3 | 4 | class EndpointControls extends React.Component { 5 | state = { 6 | loading: true 7 | } 8 | 9 | async componentWillMount() { 10 | const appService = new AppService(); 11 | 12 | this.setState({ 13 | loading: false, 14 | token: await appService.getToken(), 15 | }) 16 | } 17 | 18 | copyUrl() { 19 | this.refs.urlInput.select(); 20 | document.execCommand('copy'); 21 | } 22 | 23 | componentDidMount() { 24 | this.refs.urlInput.select(); 25 | } 26 | 27 | render() { 28 | let endpoint = null; 29 | if (!this.state.loading) { 30 | if (location.port) { 31 | endpoint = `${location.protocol}//${location.hostname}:${location.port}/${this.state.token}`; 32 | } else { 33 | endpoint = `${location.protocol}//${location.hostname}/${this.state.token}`; 34 | } 35 | } 36 | return (
37 |
38 |
39 |

This Is Your Endpoint

40 | 41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 |
54 | 55 |
56 | Send 57 |

POST

requests to store data, 58 |

PUT

and 59 |

DELETE

requests to modify or delete data, and 60 |

GET

requests to fetch data. 61 |
62 |
63 |
); 64 | } 65 | } 66 | 67 | 68 | export default EndpointControls; -------------------------------------------------------------------------------- /client/src/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Footer = () => 4 |
9 | 10 | export default Footer; -------------------------------------------------------------------------------- /client/src/components/navigator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Navigator = () => 4 | 28 | 29 | export default Navigator; -------------------------------------------------------------------------------- /client/src/html.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './components/app'; 3 | 4 | const Html = () => 5 | 6 | 7 | jsonstore.io / Store your data just by sending us HTTP Requests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/src/public/js/drift.js: -------------------------------------------------------------------------------- 1 | !function() { 2 | var t; 3 | if (t = window.driftt = window.drift = window.driftt || [], !t.init) return t.invoked ? void (window.console && console.error && console.error("Drift snippet included twice.")) : (t.invoked = !0, 4 | t.methods = [ "identify", "config", "track", "reset", "debug", "show", "ping", "page", "hide", "off", "on" ], 5 | t.factory = function(e) { 6 | return function() { 7 | var n; 8 | return n = Array.prototype.slice.call(arguments), n.unshift(e), t.push(n), t; 9 | }; 10 | }, t.methods.forEach(function(e) { 11 | t[e] = t.factory(e); 12 | }), t.load = function(t) { 13 | var e, n, o, i; 14 | e = 3e5, i = Math.ceil(new Date() / e) * e, o = document.createElement("script"), 15 | o.type = "text/javascript", o.async = !0, o.crossorigin = "anonymous", o.src = "https://js.driftt.com/include/" + i + "/" + t + ".js", 16 | n = document.getElementsByTagName("script")[0], n.parentNode.insertBefore(o, n); 17 | }); 18 | }(); 19 | drift.SNIPPET_VERSION = '0.3.1'; 20 | drift.load('mrkxm5g5ducf'); -------------------------------------------------------------------------------- /client/src/services/appService.js: -------------------------------------------------------------------------------- 1 | class AppService { 2 | getToken() { 3 | return fetch('/get-token') 4 | .then(response => response.json()) 5 | .then(json => json.token); 6 | } 7 | } 8 | 9 | export default AppService; -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluzi/jsonstore/3da991b7afeaed4342dbc7412287ae44a961c370/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "new", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "keywords": [], 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "run-s client:build:dev server:start", 10 | "prod": "PORT=80 npm start", 11 | "server:start": "node ./server", 12 | "client:build:dev": "webpack", 13 | "client:build:prod": "webpack --config webpack.config.prod.js", 14 | "heroku-postbuild": "npm run client:build:prod" 15 | }, 16 | "devDependencies": { 17 | "babel-core": "^6.10.4", 18 | "babel-loader": "^7.1.2", 19 | "babel-plugin-transform-class-properties": "^6.24.1", 20 | "babel-plugin-transform-react-jsx": "^6.24.1", 21 | "babel-preset-env": "^1.6.1", 22 | "babel-preset-es2015": "^6.24.1", 23 | "babel-preset-react": "^6.11.1", 24 | "copy-webpack-plugin": "^4.5.1", 25 | "nodemon": "^1.17.2", 26 | "npm-run-all": "^4.1.2", 27 | "webpack": "^3.8.1", 28 | "webpack-node-externals": "^1.2.0" 29 | }, 30 | "dependencies": { 31 | "babel-polyfill": "^6.26.0", 32 | "cors": "^2.8.4", 33 | "express": "^4.14.0", 34 | "firebase": "^4.12.1", 35 | "promise-fs": "^1.3.0", 36 | "react": "^16.2.0", 37 | "react-dom": "^16.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const bodyParser = require('body-parser'); 4 | const cors = require('cors'); 5 | 6 | const apiRoutes = require('./api/routes'); 7 | 8 | const port = process.env.PORT || 3000; 9 | const app = express(); 10 | 11 | app.use(express.static(__dirname + '/client/dist')); 12 | app.use(bodyParser.json({ 13 | strict: false, 14 | })); 15 | app.use(cors()); 16 | 17 | app.get('/', (req, res) => { 18 | res.sendFile(__dirname + '/client/dist/index.html'); 19 | }); 20 | 21 | app.get('/about', (req, res) => { 22 | res.send('under construction'); 23 | }); 24 | 25 | app.use(apiRoutes); 26 | 27 | app.use((err, req, res) => { 28 | res.send({ 29 | ok: false, 30 | error: 'Unexpected server error', 31 | }); 32 | }) 33 | 34 | app.listen(port); 35 | console.log(`Serving at http://localhost:${port}`); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyWebpackPlugin = require('copy-webpack-plugin') 3 | 4 | console.warn('DEVELOPMENT BUILD'); 5 | 6 | module.exports = { 7 | entry: ['./client/src/index.js'], 8 | output: { 9 | path: path.resolve(__dirname, './client/dist/'), 10 | filename: 'bundle.js', 11 | }, 12 | module: { 13 | loaders: [{ 14 | loader: 'babel-loader' 15 | }], 16 | }, 17 | plugins: [ 18 | new CopyWebpackPlugin([ 19 | { from: './client/src/public' }, 20 | ]) 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyWebpackPlugin = require('copy-webpack-plugin') 3 | const webpack = require('webpack') 4 | 5 | console.warn('PRODUCTION BUILD'); 6 | 7 | module.exports = { 8 | entry: ['./client/src/index.js'], 9 | output: { 10 | path: path.resolve(__dirname, './client/dist/'), 11 | filename: 'bundle.js', 12 | }, 13 | module: { 14 | loaders: [{ 15 | loader: 'babel-loader' 16 | }], 17 | }, 18 | plugins: [ 19 | new CopyWebpackPlugin([ 20 | { from: './client/src/public' }, 21 | ]), 22 | new webpack.DefinePlugin({ 23 | 'process.env.NODE_ENV': JSON.stringify('production') 24 | }), 25 | new webpack.optimize.UglifyJsPlugin() 26 | ] 27 | } 28 | --------------------------------------------------------------------------------