- );
- }
-});
-
-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