├── data └── .gitignore ├── public ├── css │ ├── admin.css │ ├── styles.css │ └── base.css ├── img │ └── nytlabs.png ├── js │ ├── buttons.jsx │ ├── login.jsx │ ├── initialresponse.jsx │ ├── author.jsx │ ├── response.jsx │ ├── main.jsx │ ├── responseAdmin.jsx │ ├── findAndReplaceDOMText.js │ └── admin.jsx ├── login.html ├── 404.html ├── author.html ├── 403.html ├── loggedout.html ├── initial.html ├── admin.html ├── index.html ├── home.html └── styleguide.html ├── .gitignore ├── requirements.txt ├── qanda.gif ├── Dockerfile ├── push.sh ├── main.go ├── app.json ├── LICENSE ├── DEPLOY.md ├── middle.go ├── types.go ├── README.md ├── server.go └── handlers.go /data/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* -------------------------------------------------------------------------------- /public/css/admin.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | membrane 2 | *.un~ 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | -------------------------------------------------------------------------------- /qanda.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nytlabs/membrane/HEAD/qanda.gif -------------------------------------------------------------------------------- /public/img/nytlabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nytlabs/membrane/HEAD/public/img/nytlabs.png -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM google/golang 2 | 3 | ADD ./membrane . 4 | 5 | COPY public /public 6 | 7 | EXPOSE 8080 8 | 9 | CMD ["./membrane"] 10 | -------------------------------------------------------------------------------- /push.sh: -------------------------------------------------------------------------------- 1 | go get . 2 | go build 3 | docker build -t membrane . 4 | docker tag -f membrane mikedewar/membrane 5 | docker push mikedewar/membrane 6 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | log.Println("~~~ WELCOME TO MEMBRANE ~~~~") 10 | 11 | router := NewRouter() 12 | controller.session = getSession() 13 | 14 | defer controller.session.Close() 15 | 16 | log.Fatal(http.ListenAndServe(":8080", router)) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React Tutorial Server", 3 | "description": "Code from the React tutorial", 4 | "keywords": [ "react", "reactjs", "tutorial" ], 5 | "repository": "https://github.com/reactjs/react-tutorial", 6 | "logo": "https://facebook.github.io/react/img/logo.svg", 7 | "website": "http://facebook.github.io/react/docs/tutorial.html", 8 | "success_url": "/", 9 | "env" : { 10 | "BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-nodejs.git" 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 The New York Times Company 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this library except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /DEPLOY.md: -------------------------------------------------------------------------------- 1 | # MEMBRANE DEPLOYMENT 2 | 3 | ## PREREQ 4 | On a blank ubuntu machine, install docker, then run `docker run --name local-mongo -p 27017:27017 -d mongo`. 5 | 6 | ## DEPLOY ON TEST SERVER 7 | * On your linux dev box, in the root of the membrane repository, run `./push.sh`. 8 | * login to the test server via ssh 9 | * run: 10 | ``` 11 | docker rm -f membrane 12 | docker pull mikedewar/membrane 13 | docker run -d -e MONGOIP=10.0.0.91 -e MONGOPORT=27017 -p 8888:8080 --name membrane mikedewar/membrane 14 | ``` 15 | ***YOU MUST CHANGE MONGOIP TO YOUR LOCAL IP!!*** it should be in your prompt. It will look like 10.0.0.## except the pound signs will be numbers like regular integer numbers. 16 | 17 | Now membrane is running. Confirm by waiting 10s then running `docker ps` - you should see two containers running. Visit membrane by visiting the public IP of your machine on port 8888. 18 | 19 | ## WIPING MONGO 20 | From the server, run `mongo --port 27017` to attach to the mongo database. Then: 21 | ``` 22 | db.users.drop() 23 | db.authors.drop() 24 | db.responses.drop() 25 | db.prompts.drop() 26 | ``` 27 | -------------------------------------------------------------------------------- /public/js/buttons.jsx: -------------------------------------------------------------------------------- 1 | var LogOutButton = React.createClass({ 2 | 3 | logout: function() { 4 | $.ajax({ 5 | url: "/logout", 6 | type: "POST", 7 | statusCode: { 8 | 200: function() { 9 | window.location.replace("/loggedOut") 10 | } 11 | } 12 | }) 13 | }, 14 | 15 | render: function() { 16 | return ( 17 |
Log out
18 | ) 19 | } 20 | 21 | }) 22 | 23 | var DeleteButton = React.createClass({ 24 | 25 | propTypes: { 26 | slug: React.PropTypes.string.isRequired 27 | }, 28 | 29 | 30 | deletePost: function(){ 31 | $.ajax({ 32 | url: "/responses/"+this.props.slug, 33 | type: "DELETE", 34 | statusCode : { 35 | 200: function(){ 36 | window.location.reload() 37 | }, 38 | } 39 | }) 40 | }, 41 | 42 | render: function() { 43 | return ( 44 |
45 | ) 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Membrane Login 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Membrane 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

Sorry! The URL you have requested does not exist (404).

18 |
19 | 20 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/author.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Membrane Admin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /public/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Membrane 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

Hi this page is Forbidden to you! (403). If you are a membrane author, please login

18 |
19 | 20 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/loggedout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Membrane 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |

You are logged out! We've removed the cookie from your browser, so now you look just like any old person to us now. If you want to, please log back in.

18 |
19 | 20 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/js/login.jsx: -------------------------------------------------------------------------------- 1 | var Page = React.createClass({ 2 | render: function() { 3 | return ( 4 |
5 |

Author login

6 | 7 |
8 | ) 9 | } 10 | }) 11 | 12 | var LoginForm = React.createClass({ 13 | submitLogin: function(event) { 14 | event.preventDefault() 15 | var data = { 16 | username: React.findDOMNode(this.refs.username).value, 17 | password: React.findDOMNode(this.refs.password).value 18 | } 19 | 20 | if (this.validateLogin(data)) { 21 | this.postToLoginEndpoint(data); 22 | } 23 | }, 24 | 25 | validateLogin: function(data) { 26 | if (data.username != undefined && data.password != undefined) { 27 | return true 28 | } 29 | return false 30 | }, 31 | 32 | postToLoginEndpoint: function(data) { 33 | $.ajax({ 34 | url: "/login", 35 | type: "POST", 36 | dataType: "json", 37 | data: JSON.stringify(data), 38 | statusCode: { 39 | 200: function() { 40 | window.location.reload() 41 | } 42 | 43 | } 44 | }) 45 | }, 46 | 47 | render: function() { 48 | return ( 49 |
50 |
51 |

52 |

53 |

54 |
55 |
56 | ) 57 | } 58 | }) 59 | 60 | React.render( 61 | , document.getElementById("content") 62 | ) 63 | -------------------------------------------------------------------------------- /public/initial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Membrane Admin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /public/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Membrane Admin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Membrane 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
Thank you! Check back here in the next two days to see any replies from the author.
24 |
25 |
26 | 27 |
28 |
29 | 30 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /middle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "strings" 7 | "time" 8 | 9 | "github.com/gorilla/context" 10 | "gopkg.in/mgo.v2/bson" 11 | ) 12 | 13 | func GetResponse(inner http.Handler) http.Handler { 14 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | slug := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:] 16 | var response Response 17 | c := controller.session.DB("test").C("responses") 18 | query := c.Find(bson.M{"slug": slug}) 19 | err := query.One(&response) 20 | if err != nil { 21 | log.Println("looking for slug:", slug) 22 | log.Println("in getReponse middleware", err) 23 | notFound(w, r) 24 | return 25 | } 26 | context.Set(r, "response", response) 27 | inner.ServeHTTP(w, r) 28 | }) 29 | } 30 | 31 | func GetSlug(inner http.Handler) http.Handler { 32 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 33 | slug := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:] 34 | context.Set(r, "slug", slug) 35 | inner.ServeHTTP(w, r) 36 | }) 37 | } 38 | 39 | func GetCookie(inner http.Handler, cookieName string) http.Handler { 40 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 41 | var value string 42 | cookie, err := r.Cookie(cookieName) 43 | if err != nil { 44 | log.Println("WARNING: could not find cookie", cookieName) 45 | goto setAndReturn 46 | } 47 | err = cookieHandler.Decode(cookieName, cookie.Value, &value) 48 | if err != nil { 49 | log.Println("WARNING:found cookie but could not decode") 50 | } 51 | setAndReturn: 52 | context.Set(r, cookieName, value) 53 | inner.ServeHTTP(w, r) 54 | }) 55 | } 56 | 57 | func GetAuthorFromCookie(inner http.Handler) http.Handler { 58 | return GetCookie(inner, "membraneAuthor") 59 | } 60 | 61 | func GetReaderFromCookie(inner http.Handler) http.Handler { 62 | return GetCookie(inner, "membraneReader") 63 | } 64 | 65 | func Logger(inner http.Handler, name string) http.Handler { 66 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 67 | start := time.Now() 68 | 69 | inner.ServeHTTP(w, r) 70 | 71 | log.Printf( 72 | "%s\t%s\t%s\t%s", 73 | r.Method, 74 | r.RequestURI, 75 | name, 76 | time.Since(start), 77 | ) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /public/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Membrane 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | Membrane 32 |
33 | 34 | 35 |
36 | 37 |

The Future of Community

38 |
39 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
40 | 41 |
63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /public/js/initialresponse.jsx: -------------------------------------------------------------------------------- 1 | var Page = React.createClass({ 2 | getInitialState: function() { 3 | return {} 4 | }, 5 | 6 | render: function() { 7 | return ( 8 |
9 | 15 |
16 |
17 |

Write your first piece of text.

18 | 19 |
20 |
21 |
22 | ) 23 | } 24 | }) 25 | 26 | var AuthorPanel = React.createClass({ 27 | postToResponsesEndpoint: function() { 28 | var con = this; 29 | var text = $(this.refs.text.getDOMNode()).val(); 30 | // var links = $(this.refs.links.getDOMNode()).val().trim(); 31 | 32 | // links = links.split(", ") 33 | 34 | // var linkInfo = []; 35 | // links.forEach(function(val, index) { 36 | // var lastChar = val.charAt(val.length-1); 37 | // var text = val; 38 | // if (lastChar == ",") { 39 | // text = text.slice(0, -1); 40 | // } 41 | // linkInfo.push(text) 42 | // }) 43 | 44 | var linkInfo = { 45 | "text": "another", 46 | "href": "http://google.com" 47 | } 48 | 49 | var data = { 50 | text: text, 51 | parent: [], 52 | // links: linkInfo 53 | } 54 | 55 | console.log(data); 56 | console.log(JSON.stringify(data)) 57 | 58 | $.ajax({ 59 | url: "/responses", 60 | dataType: "json", 61 | type: "POST", 62 | data: JSON.stringify(data), 63 | success: function() { 64 | window.location = window.location.href.split("/")[0] + "/admin" 65 | } 66 | }) 67 | }, 68 | 69 | 70 | render: function() { 71 | return ( 72 |
73 |