diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..052e5ee7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{js,rb,css,html}] +indent_size = 2 + +[*.go] +indent_size = 8 +indent_style = tab diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..87f537d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +The examples provided by Facebook are for non-commercial testing and evaluation +purposes only. Facebook reserves all rights not expressly granted. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index cb7dabbc..4862f5df 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,55 @@ -# React comment box example +[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) + +# React Tutorial This is the React comment box example from [the React tutorial](http://facebook.github.io/react/docs/tutorial.html). ## To use -``` +There are several simple server implementations included. They all serve static files from `public/` and handle requests to `/api/comments` to fetch or add data. Start a server with one of the following: + +### Node + +```sh npm install node server.js ``` -And visit http://localhost:3000/. Try opening multiple tabs! +### Python + +```sh +pip install -r requirements.txt +python server.py +``` + +### Ruby +```sh +ruby server.rb +``` + +### PHP +```sh +php server.php +``` + +### Go +```sh +go run server.go +``` + +### Perl + +```sh +cpan Mojolicious +perl server.pl +``` + +And visit . Try opening multiple tabs! + +## Changing the port + +You can change the port number by setting the `$PORT` environment variable before invoking any of the scripts above, e.g., + +```sh +PORT=3001 node server.js +``` diff --git a/app.json b/app.json new file mode 100644 index 00000000..aa3e6afd --- /dev/null +++ b/app.json @@ -0,0 +1,13 @@ +{ + "name": "React Tutorial Server", + "description": "Code from the React tutorial", + "keywords": [ "react", "reactjs", "tutorial" ], + "repository": "https://github.com/reactjs/react-tutorial", + "logo": "https://facebook.github.io/react/img/logo.svg", + "website": "http://facebook.github.io/react/docs/tutorial.html", + "success_url": "/", + "env" : { + "BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-nodejs.git" + } +} + diff --git a/comments.json b/comments.json new file mode 100644 index 00000000..28816136 --- /dev/null +++ b/comments.json @@ -0,0 +1,27 @@ +[ + { + "id": 1388534400000, + "author": "Pete Hunt", + "text": "Hey there!" + }, + { + "id": 1420070400000, + "author": "Paul O’Shannessy", + "text": "React is *great*!" + }, + { + "id": 1464988635157, + "author": "ben", + "text": "*abc*" + }, + { + "id": 1464988636500, + "author": "ben", + "text": "*abc*" + }, + { + "id": 1464988717637, + "author": "evil", + "text": "alert(1)" + } +] \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100755 index defed026..00000000 --- a/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - Hello React - - - - - - - - -
- - - diff --git a/package.json b/package.json index d1ce0454..bf3360a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { "name": "react-tutorial", "version": "0.0.0", + "private": true, + "license": "see LICENSE file", "description": "Code from the React tutorial.", "main": "server.js", "dependencies": { @@ -23,9 +25,11 @@ "example" ], "author": "petehunt", - "license": "MIT", "bugs": { "url": "https://github.com/reactjs/react-tutorial/issues" }, - "homepage": "https://github.com/reactjs/react-tutorial" + "homepage": "https://github.com/reactjs/react-tutorial", + "engines" : { + "node" : "0.12.x" + } } diff --git a/css/base.css b/public/css/base.css old mode 100755 new mode 100644 similarity index 83% rename from css/base.css rename to public/css/base.css index 3ed9ea38..c8cc35f7 --- a/css/base.css +++ b/public/css/base.css @@ -1,6 +1,6 @@ body { background: #fff; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 15px; line-height: 1.7; margin: 0; @@ -23,7 +23,7 @@ code { font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; font-size: 12px; margin: 0 2px; - padding: 0px 5px; + padding: 0 5px; } h1, h2, h3, h4 { @@ -35,9 +35,6 @@ h1, h2, h3, h4 { h1 { border-bottom: 1px solid #ddd; font-size: 2.5em; - font-weight: bold; - margin: 0 0 15px; - padding: 0; } h2 { diff --git a/public/index.html b/public/index.html new file mode 100644 index 00000000..eb201204 --- /dev/null +++ b/public/index.html @@ -0,0 +1,22 @@ + + + + + React Tutorial + + + + + + + + + +
+ + + + diff --git a/public/scripts/example.js b/public/scripts/example.js new file mode 100644 index 00000000..6493fea9 --- /dev/null +++ b/public/scripts/example.js @@ -0,0 +1,147 @@ +/** + * This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +var Comment = React.createClass({ + rawMarkup: function() { + var md = new Remarkable(); + var rawMarkup = md.render(this.props.children.toString()); + return { __html: rawMarkup }; + }, + + render: function() { + return ( +
+

+ {this.props.author} +

+ +
+ ); + } +}); + +var CommentBox = React.createClass({ + loadCommentsFromServer: function() { + $.ajax({ + url: this.props.url, + dataType: 'json', + cache: false, + success: function(data) { + this.setState({data: data}); + }.bind(this), + error: function(xhr, status, err) { + console.error(this.props.url, status, err.toString()); + }.bind(this) + }); + }, + handleCommentSubmit: function(comment) { + var comments = this.state.data; + // Optimistically set an id on the new comment. It will be replaced by an + // id generated by the server. In a production application you would likely + // not use Date.now() for this and would have a more robust system in place. + comment.id = Date.now(); + var newComments = comments.concat([comment]); + this.setState({data: newComments}); + $.ajax({ + url: this.props.url, + dataType: 'json', + type: 'POST', + data: comment, + success: function(data) { + this.setState({data: data}); + }.bind(this), + error: function(xhr, status, err) { + this.setState({data: comments}); + console.error(this.props.url, status, err.toString()); + }.bind(this) + }); + }, + getInitialState: function() { + return {data: []}; + }, + componentDidMount: function() { + this.loadCommentsFromServer(); + setInterval(this.loadCommentsFromServer, this.props.pollInterval); + }, + render: function() { + return ( +
+

Comments

+ + +
+ ); + } +}); + +var CommentList = React.createClass({ + render: function() { + var commentNodes = this.props.data.map(function(comment) { + return ( + + {comment.text} + + ); + }); + return ( +
+ {commentNodes} +
+ ); + } +}); + +var CommentForm = React.createClass({ + getInitialState: function() { + return {author: '', text: ''}; + }, + handleAuthorChange: function(e) { + this.setState({author: e.target.value}); + }, + handleTextChange: function(e) { + this.setState({text: e.target.value}); + }, + handleSubmit: function(e) { + e.preventDefault(); + var author = this.state.author.trim(); + var text = this.state.text.trim(); + if (!text || !author) { + return; + } + this.props.onCommentSubmit({author: author, text: text}); + this.setState({author: '', text: ''}); + }, + render: function() { + return ( +
+ + + +
+ ); + } +}); + +ReactDOM.render( + , + document.getElementById('content') +); diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..632a1efa --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Flask==0.10.1 diff --git a/scripts/example.js b/scripts/example.js deleted file mode 100755 index 0d316b6e..00000000 --- a/scripts/example.js +++ /dev/null @@ -1,118 +0,0 @@ -/** @jsx React.DOM */ - -var converter = new Showdown.converter(); - -var Comment = React.createClass({ - render: function() { - var rawMarkup = converter.makeHtml(this.props.children.toString()); - return ( -
-

- {this.props.author} -

- -
- ); - } -}); - -var CommentBox = React.createClass({ - loadCommentsFromServer: function() { - $.ajax({ - url: this.props.url, - dataType: 'json', - success: function(data) { - this.setState({data: data}); - }.bind(this), - error: function(xhr, status, err) { - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); - }, - handleCommentSubmit: function(comment) { - var comments = this.state.data; - comments.push(comment); - this.setState({data: comments}, function() { - // `setState` accepts a callback. To avoid (improbable) race condition, - // `we'll send the ajax request right after we optimistically set the new - // `state. - $.ajax({ - url: this.props.url, - dataType: 'json', - type: 'POST', - data: comment, - success: function(data) { - this.setState({data: data}); - }.bind(this), - error: function(xhr, status, err) { - console.error(this.props.url, status, err.toString()); - }.bind(this) - }); - }); - }, - getInitialState: function() { - return {data: []}; - }, - componentDidMount: function() { - this.loadCommentsFromServer(); - setInterval(this.loadCommentsFromServer, this.props.pollInterval); - }, - render: function() { - return ( -
-

Comments

- - -
- ); - } -}); - -var CommentList = React.createClass({ - render: function() { - var commentNodes = this.props.data.map(function(comment, index) { - return ( - // `key` is a React-specific concept and is not mandatory for the - // purpose of this tutorial. if you're curious, see more here: - // http://facebook.github.io/react/docs/multiple-components.html#dynamic-children - - {comment.text} - - ); - }); - return ( -
- {commentNodes} -
- ); - } -}); - -var CommentForm = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - var author = this.refs.author.getDOMNode().value.trim(); - var text = this.refs.text.getDOMNode().value.trim(); - if (!text || !author) { - return; - } - this.props.onCommentSubmit({author: author, text: text}); - this.refs.author.getDOMNode().value = ''; - this.refs.text.getDOMNode().value = ''; - return; - }, - render: function() { - return ( -
- - - -
- ); - } -}); - -React.renderComponent( - , - document.getElementById('content') -); diff --git a/server.go b/server.go new file mode 100644 index 00000000..934a4cfc --- /dev/null +++ b/server.go @@ -0,0 +1,112 @@ +/** + * This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "sync" + "time" +) + +type comment struct { + ID int64 `json:"id"` + Author string `json:"author"` + Text string `json:"text"` +} + +const dataFile = "./comments.json" + +var commentMutex = new(sync.Mutex) + +// Handle comments +func handleComments(w http.ResponseWriter, r *http.Request) { + // Since multiple requests could come in at once, ensure we have a lock + // around all file operations + commentMutex.Lock() + defer commentMutex.Unlock() + + // Stat the file, so we can find its current permissions + fi, err := os.Stat(dataFile) + if err != nil { + http.Error(w, fmt.Sprintf("Unable to stat the data file (%s): %s", dataFile, err), http.StatusInternalServerError) + return + } + + // Read the comments from the file. + commentData, err := ioutil.ReadFile(dataFile) + if err != nil { + http.Error(w, fmt.Sprintf("Unable to read the data file (%s): %s", dataFile, err), http.StatusInternalServerError) + return + } + + switch r.Method { + case "POST": + // Decode the JSON data + var comments []comment + if err := json.Unmarshal(commentData, &comments); err != nil { + http.Error(w, fmt.Sprintf("Unable to Unmarshal comments from data file (%s): %s", dataFile, err), http.StatusInternalServerError) + return + } + + // Add a new comment to the in memory slice of comments + comments = append(comments, comment{ID: time.Now().UnixNano() / 1000000, Author: r.FormValue("author"), Text: r.FormValue("text")}) + + // Marshal the comments to indented json. + commentData, err = json.MarshalIndent(comments, "", " ") + if err != nil { + http.Error(w, fmt.Sprintf("Unable to marshal comments to json: %s", err), http.StatusInternalServerError) + return + } + + // Write out the comments to the file, preserving permissions + err := ioutil.WriteFile(dataFile, commentData, fi.Mode()) + if err != nil { + http.Error(w, fmt.Sprintf("Unable to write comments to data file (%s): %s", dataFile, err), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Access-Control-Allow-Origin", "*") + io.Copy(w, bytes.NewReader(commentData)) + + case "GET": + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Access-Control-Allow-Origin", "*") + // stream the contents of the file to the response + io.Copy(w, bytes.NewReader(commentData)) + + default: + // Don't know the method, so error + http.Error(w, fmt.Sprintf("Unsupported method: %s", r.Method), http.StatusMethodNotAllowed) + } +} + +func main() { + port := os.Getenv("PORT") + if port == "" { + port = "3000" + } + http.HandleFunc("/api/comments", handleComments) + http.Handle("/", http.FileServer(http.Dir("./public"))) + log.Println("Server started: http://localhost:" + port) + log.Fatal(http.ListenAndServe(":"+port, nil)) +} diff --git a/server.js b/server.js index 7724fcf1..b5a7218a 100644 --- a/server.js +++ b/server.js @@ -1,24 +1,77 @@ +/** + * This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +var fs = require('fs'); +var path = require('path'); var express = require('express'); var bodyParser = require('body-parser'); var app = express(); -var comments = [{author: 'Pete Hunt', text: 'Hey there!'}]; +var COMMENTS_FILE = path.join(__dirname, 'comments.json'); + +app.set('port', (process.env.PORT || 3000)); -app.use('/', express.static(__dirname)); +app.use('/', express.static(path.join(__dirname, 'public'))); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); -app.get('/comments.json', function(req, res) { - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(comments)); +// Additional middleware which will set headers that we need on each request. +app.use(function(req, res, next) { + // Set permissive CORS header - this allows this server to be used only as + // an API server in conjunction with something like webpack-dev-server. + res.setHeader('Access-Control-Allow-Origin', '*'); + + // Disable caching so we'll always get the latest comments. + res.setHeader('Cache-Control', 'no-cache'); + next(); }); -app.post('/comments.json', function(req, res) { - comments.push(req.body); - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(comments)); +app.get('/api/comments', function(req, res) { + fs.readFile(COMMENTS_FILE, function(err, data) { + if (err) { + console.error(err); + process.exit(1); + } + res.json(JSON.parse(data)); + }); +}); + +app.post('/api/comments', function(req, res) { + fs.readFile(COMMENTS_FILE, function(err, data) { + if (err) { + console.error(err); + process.exit(1); + } + var comments = JSON.parse(data); + // NOTE: In a real implementation, we would likely rely on a database or + // some other approach (e.g. UUIDs) to ensure a globally unique id. We'll + // treat Date.now() as unique-enough for our purposes. + var newComment = { + id: Date.now(), + author: req.body.author, + text: req.body.text, + }; + comments.push(newComment); + fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) { + if (err) { + console.error(err); + process.exit(1); + } + res.json(comments); + }); + }); }); -app.listen(3000); -console.log('Server started: http://localhost:3000/'); +app.listen(app.get('port'), function() { + console.log('Server started: http://localhost:' + app.get('port') + '/'); +}); diff --git a/server.php b/server.php new file mode 100644 index 00000000..e510136b --- /dev/null +++ b/server.php @@ -0,0 +1,53 @@ + round(microtime(true) * 1000), + 'author' => $_POST['author'], + 'text' => $_POST['text'] + ]; + + $comments = json_encode($commentsDecoded, JSON_PRETTY_PRINT); + file_put_contents('comments.json', $comments); + } + header('Content-Type: application/json'); + header('Cache-Control: no-cache'); + header('Access-Control-Allow-Origin: *'); + echo $comments; + } else { + return false; + } +} diff --git a/server.pl b/server.pl new file mode 100644 index 00000000..c3212b9c --- /dev/null +++ b/server.pl @@ -0,0 +1,38 @@ +# This file provided by Facebook is for non-commercial testing and evaluation +# purposes only. Facebook reserves all rights not expressly granted. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +use Time::HiRes qw(gettimeofday); +use Mojolicious::Lite; +use Mojo::JSON qw(encode_json decode_json); + +app->static->paths->[0] = './public'; + +any '/' => sub { $_[0]->reply->static('index.html') }; + +any [qw(GET POST)] => '/api/comments' => sub { + my $self = shift; + my $comments = decode_json (do { local(@ARGV,$/) = 'comments.json';<> }); + $self->res->headers->cache_control('no-cache'); + $self->res->headers->access_control_allow_origin('*'); + + if ($self->req->method eq 'POST') + { + push @$comments, { + id => int(gettimeofday * 1000), + author => $self->param('author'), + text => $self->param('text'), + }; + open my $FILE, '>', 'comments.json'; + print $FILE encode_json($comments); + } + $self->render(json => $comments); +}; +my $port = $ENV{PORT} || 3000; +app->start('daemon', '-l', "http://*:$port"); diff --git a/server.py b/server.py new file mode 100644 index 00000000..03c6213d --- /dev/null +++ b/server.py @@ -0,0 +1,44 @@ +# This file provided by Facebook is for non-commercial testing and evaluation +# purposes only. Facebook reserves all rights not expressly granted. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import json +import os +import time +from flask import Flask, Response, request + +app = Flask(__name__, static_url_path='', static_folder='public') +app.add_url_rule('/', 'root', lambda: app.send_static_file('index.html')) + + +@app.route('/api/comments', methods=['GET', 'POST']) +def comments_handler(): + with open('comments.json', 'r') as f: + comments = json.loads(f.read()) + + if request.method == 'POST': + new_comment = request.form.to_dict() + new_comment['id'] = int(time.time() * 1000) + comments.append(new_comment) + + with open('comments.json', 'w') as f: + f.write(json.dumps(comments, indent=4, separators=(',', ': '))) + + return Response( + json.dumps(comments), + mimetype='application/json', + headers={ + 'Cache-Control': 'no-cache', + 'Access-Control-Allow-Origin': '*' + } + ) + + +if __name__ == '__main__': + app.run(port=int(os.environ.get("PORT", 3000)), debug=True) diff --git a/server.rb b/server.rb new file mode 100644 index 00000000..698f4339 --- /dev/null +++ b/server.rb @@ -0,0 +1,49 @@ +# This file provided by Facebook is for non-commercial testing and evaluation +# purposes only. Facebook reserves all rights not expressly granted. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +require 'webrick' +require 'json' + +# default port to 3000 or overwrite with PORT variable by running +# $ PORT=3001 ruby server.rb +port = ENV['PORT'] ? ENV['PORT'].to_i : 3000 + +puts "Server started: http://localhost:#{port}/" + +root = File.expand_path './public' +server = WEBrick::HTTPServer.new Port: port, DocumentRoot: root + +server.mount_proc '/api/comments' do |req, res| + comments = JSON.parse(File.read('./comments.json', encoding: 'UTF-8')) + + if req.request_method == 'POST' + # Assume it's well formed + comment = { id: (Time.now.to_f * 1000).to_i } + req.query.each do |key, value| + comment[key] = value.force_encoding('UTF-8') unless key == 'id' + end + comments << comment + File.write( + './comments.json', + JSON.pretty_generate(comments, indent: ' '), + encoding: 'UTF-8' + ) + end + + # always return json + res['Content-Type'] = 'application/json' + res['Cache-Control'] = 'no-cache' + res['Access-Control-Allow-Origin'] = '*' + res.body = JSON.generate(comments) +end + +trap('INT') { server.shutdown } + +server.start