├── .gitignore ├── .nodemonignore ├── app ├── stores │ ├── index.js │ ├── messagesStore.js │ └── roomsStore.js ├── queries │ ├── index.js │ ├── messageQueries.js │ └── roomQueries.js ├── server │ ├── db │ │ └── index.js │ ├── views │ │ └── index.ejs │ ├── rooms.js │ └── index.js ├── sources │ ├── index.js │ ├── roomsAPI.js │ ├── messagesAPI.js │ └── serverUpdatesSocket.js ├── styles │ └── index.css ├── constants │ ├── roomConstants.js │ └── messageConstants.js ├── actions │ ├── index.js │ ├── navigationActionCreators.js │ ├── roomActionCreators.js │ └── messageActionCreators.js ├── routes.js ├── router.js ├── utils │ ├── roomUtils.js │ └── messageUtils.js ├── application.js ├── index.js └── components │ ├── newMessage.js │ ├── home.js │ ├── newRoom.js │ └── room.js ├── Makefile ├── .jshintrc ├── README.md ├── package.json └── Gruntfile.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | dist 4 | app/server/db/blob.json -------------------------------------------------------------------------------- /.nodemonignore: -------------------------------------------------------------------------------- 1 | tmp/* 2 | dist/ 3 | server/views/* 4 | app/* 5 | log/* 6 | .git/* 7 | test/* -------------------------------------------------------------------------------- /app/stores/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roomsStore: require('./roomsStore'), 3 | messagesStore: require('./messagesStore') 4 | }; -------------------------------------------------------------------------------- /app/queries/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roomQueries: require('./roomQueries'), 3 | messageQueries: require('./messageQueries') 4 | }; -------------------------------------------------------------------------------- /app/server/db/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var DB = require('super-simple-db'); 3 | 4 | module.exports = new DB(path.join(__dirname, 'blob.json')); -------------------------------------------------------------------------------- /app/sources/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roomsAPI: require('./roomsAPI'), 3 | messagesAPI: require('./messagesAPI'), 4 | serverUpdatesSocket: require('./serverUpdatesSocket') 5 | }; -------------------------------------------------------------------------------- /app/styles/index.css: -------------------------------------------------------------------------------- 1 | .rooms, .messages { 2 | margin: 0; 3 | padding: 0; 4 | list-style-type: none; 5 | } 6 | 7 | .rooms .room, .messages .message { 8 | margin: 20px 0; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /app/constants/roomConstants.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | 3 | var RoomConstants = Marty.createConstants([ 4 | 'RECEIVE_ROOMS', 5 | 'UPDATE_ROOM' 6 | ]); 7 | 8 | module.exports = RoomConstants; -------------------------------------------------------------------------------- /app/constants/messageConstants.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | 3 | var MessageConstants = Marty.createConstants([ 4 | 'RECEIVE_MESSAGES', 5 | 'UPDATE_MESSAGE' 6 | ]); 7 | 8 | module.exports = MessageConstants; -------------------------------------------------------------------------------- /app/actions/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | messageActionCreators: require('./messageActionCreators'), 3 | navigationActionCreators: require('./navigationActionCreators'), 4 | roomActionCreators: require('./roomActionCreators') 5 | }; -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = ./node_modules/.bin 2 | 3 | .PHONY: start bootstrap test; 4 | 5 | SRC = $(shell find ./app ./test -type f -name '*.js') 6 | 7 | start: 8 | @$(BIN)/grunt 9 | 10 | test: 11 | @$(BIN)/mocha 12 | 13 | bootstrap: package.json 14 | @npm install -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var Route = Router.Route; 4 | 5 | module.exports = [ 6 | , 7 | 8 | ]; -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | var Router = require('react-router'); 3 | 4 | module.exports = Router.create({ 5 | routes: require('./routes'), 6 | location: location() 7 | }); 8 | 9 | function location() { 10 | if (typeof window !== 'undefined') { 11 | return Router.HistoryLocation; 12 | } 13 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": true, 16 | "strict": true 17 | } 18 | -------------------------------------------------------------------------------- /app/utils/roomUtils.js: -------------------------------------------------------------------------------- 1 | var uuid = require('uuid').v1; 2 | var _ = require('lodash'); 3 | 4 | module.exports = { 5 | createRoom: function (name) { 6 | return { 7 | id: uuid(), 8 | name: name, 9 | messages: [], 10 | cid: this.cid() 11 | }; 12 | }, 13 | cid: function () { 14 | return _.uniqueId('room'); 15 | } 16 | }; -------------------------------------------------------------------------------- /app/application.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | 3 | var Application = Marty.createApplication(function () { 4 | this.register(require('./stores')); 5 | this.register(require('./actions')); 6 | this.register(require('./queries')); 7 | this.register(require('./sources')); 8 | this.router = require('./router'); 9 | }); 10 | 11 | module.exports = Application; -------------------------------------------------------------------------------- /app/utils/messageUtils.js: -------------------------------------------------------------------------------- 1 | var uuid = require('uuid').v1; 2 | var _ = require('lodash'); 3 | 4 | module.exports = { 5 | createMessage: function (text, roomId) { 6 | return { 7 | text: text, 8 | id: uuid(), 9 | roomId: roomId, 10 | cid: this.cid(), 11 | timestamp: new Date().toJSON() 12 | }; 13 | }, 14 | cid: function () { 15 | return _.uniqueId('message'); 16 | } 17 | }; -------------------------------------------------------------------------------- /app/actions/navigationActionCreators.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | var Router = require('../router'); 3 | 4 | var NavigationActionCreators = Marty.createActionCreators({ 5 | navigateHome: function () { 6 | navigateTo('home'); 7 | }, 8 | navigateToRoom: function (id) { 9 | navigateTo('room', { id: id }); 10 | } 11 | }); 12 | 13 | function navigateTo(route, params) { 14 | require('../router').transitionTo(route, params || {}); 15 | } 16 | 17 | module.exports = NavigationActionCreators; -------------------------------------------------------------------------------- /app/sources/roomsAPI.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var Marty = require('marty'); 3 | 4 | var RoomHttpAPI = Marty.createStateSource({ 5 | type: 'http', 6 | id: 'RoomHttpAPI', 7 | getAllRooms: function () { 8 | return this.get('/api/rooms'); 9 | }, 10 | getRoom: function (id) { 11 | return this.get('/api/rooms/' + id); 12 | }, 13 | createRoom: function (room) { 14 | return this.post({ 15 | url: '/api/rooms', 16 | body: _.omit(room, 'cid') 17 | }); 18 | } 19 | }); 20 | 21 | module.exports = RoomHttpAPI; -------------------------------------------------------------------------------- /app/queries/messageQueries.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var Marty = require('marty'); 3 | var format = require('util').format; 4 | var MessageConstants = require('../constants/messageConstants'); 5 | 6 | var MessageQueries = Marty.createQueries({ 7 | id: 'MessageQueries', 8 | getMessagesForRoom: function (roomId) { 9 | return this.app.messagesAPI.getMessagesForRoom(roomId).then((function (res) { 10 | this.dispatch(MessageConstants.RECEIVE_MESSAGES, roomId, res.body); 11 | }).bind(this)); 12 | } 13 | }); 14 | 15 | module.exports = MessageQueries; -------------------------------------------------------------------------------- /app/sources/messagesAPI.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | var _ = require('lodash'); 3 | var format = require('util').format; 4 | 5 | var MessageHttpAPI = Marty.createStateSource({ 6 | type: 'http', 7 | id: 'MessageHttpAPI', 8 | getMessagesForRoom: function (roomId) { 9 | return this.get(format('/api/rooms/%s/messages', roomId)); 10 | }, 11 | createMessage: function (message) { 12 | return this.post({ 13 | body: _.omit(message, 'cid'), 14 | url: format('/api/rooms/%s/messages', message.roomId) 15 | }); 16 | } 17 | }); 18 | 19 | module.exports = MessageHttpAPI; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##How do I? 2 | 3 | * Install all dependencies ```make bootstrap``` 4 | * Run the application ```make start``` 5 | * Deploy ```make deploy``` 6 | 7 | 8 | ##HTTP Endpoints 9 | 10 | * ``GET /rooms``: List of rooms 11 | * ``GET /rooms/:roomId``: Get room 12 | * ``GET /rooms/:roomId/messages``: Messages in a room 13 | * ``POST /rooms/:roomId/messages``: Post message to a room 14 | 15 | ###Server events 16 | 17 | * ``message``: A message has been sent 18 | * ``room:created``: A new room has been created 19 | 20 | ###Message schema 21 | 22 | ``` 23 | { 24 | text: "Hello world", 25 | timestamp: "2014-09-05T21:29:01.000Z", 26 | roomId: "8a720550-a09f-11e4-b472-c7ff2cae8c5f" 27 | } 28 | ``` -------------------------------------------------------------------------------- /app/queries/roomQueries.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var Marty = require('marty'); 3 | var format = require('util').format; 4 | var RoomConstants = require('../constants/roomConstants'); 5 | 6 | var RoomQueries = Marty.createQueries({ 7 | id: 'RoomQueries', 8 | getAllRooms: function () { 9 | return this.app.roomsAPI.getAllRooms().then((function (res) { 10 | return this.dispatch(RoomConstants.RECEIVE_ROOMS, res.body); 11 | }).bind(this)); 12 | }, 13 | getRoom: function (id) { 14 | return this.app.roomsAPI.getRoom(id).then((function (res) { 15 | this.dispatch(RoomConstants.RECEIVE_ROOMS, res.body); 16 | }).bind(this)); 17 | }, 18 | }); 19 | 20 | module.exports = RoomQueries; -------------------------------------------------------------------------------- /app/actions/roomActionCreators.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | var RoomUtils = require('../utils/roomUtils'); 3 | var RoomConstants = require('../constants/roomConstants'); 4 | 5 | var RoomActionCreators = Marty.createActionCreators({ 6 | id: 'RoomActionCreators', 7 | createRoom: function (name) { 8 | var room = RoomUtils.createRoom(name); 9 | 10 | this.dispatch(RoomConstants.RECEIVE_ROOMS, room); 11 | 12 | this.app.roomsAPI.createRoom(room).then(function (res) { 13 | this.dispatch(RoomConstants.UPDATE_ROOM, room.cid, res.body); 14 | }.bind(this)) 15 | }, 16 | receiveRooms: function (rooms) { 17 | this.dispatch(RoomConstants.RECEIVE_ROOMS, rooms); 18 | } 19 | }); 20 | 21 | module.exports = RoomActionCreators; -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Marty = require('marty'); 3 | var Application = require('./application'); 4 | var ApplicationContainer = Marty.ApplicationContainer; 5 | 6 | window.React = React; // For React Developer Tools 7 | window.Marty = Marty; // For Marty Developer Tools 8 | 9 | if (process.env.NODE_ENV !== 'test') { 10 | var app = new Application(); 11 | 12 | app.rehydrate(); 13 | 14 | if (Marty.isBrowser) { 15 | app.serverUpdatesSocket.open(); 16 | } 17 | 18 | app.router.run(function (Handler, state) { 19 | React.render(( 20 | 21 | 22 | 23 | ), document.getElementById('app')); 24 | }); 25 | } -------------------------------------------------------------------------------- /app/actions/messageActionCreators.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | var MessageUtils = require('../utils/messageUtils'); 3 | var MessageConstants = require('../constants/messageConstants'); 4 | 5 | var MessageActionCreators = Marty.createActionCreators({ 6 | id: 'MessageActionCreators', 7 | sendMessage: function (text, roomId) { 8 | var message = MessageUtils.createMessage(text, roomId); 9 | 10 | this.dispatch(MessageConstants.RECEIVE_MESSAGES, roomId, message); 11 | 12 | this.app.messagesAPI.createMessage(message).then(function (res) { 13 | this.dispatch(MessageConstants.UPDATE_MESSAGE, message.cid, res.body); 14 | }.bind(this)) 15 | }, 16 | receiveMessages: function (roomId, messages) { 17 | this.dispatch(MessageConstants.RECEIVE_MESSAGES, roomId, messages); 18 | } 19 | }); 20 | 21 | module.exports = MessageActionCreators; 22 | -------------------------------------------------------------------------------- /app/server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | marty-chat-example 5 | 6 | 7 | 8 | 9 | 16 |
17 |
18 | <%- body %> 19 |
20 |
21 | <%- state %> 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/sources/serverUpdatesSocket.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | var RoomsStore = require('../stores/roomsStore'); 3 | var MessagesStore = require('../stores/messagesStore'); 4 | var SocketStateSource = require('marty-socket.io-state-source'); 5 | var RoomActionCreators = require('../actions/roomActionCreators'); 6 | var MessageActionCreators = require('../actions/messageActionCreators'); 7 | 8 | var ServerUpdatesSocket = Marty.createStateSource({ 9 | id: 'ServerUpdatesSocket', 10 | mixins: [SocketStateSource()], 11 | events: { 12 | 'message': 'onMessage', 13 | 'room:created': 'onRoomCreated' 14 | }, 15 | onMessage: function (message) { 16 | if (!this.app.messagesStore.getMessage(message.id, message.roomId)) { 17 | this.app.messageActionCreators.receiveMessages(message.roomId, [message]); 18 | } 19 | }, 20 | onRoomCreated: function (room) { 21 | if (!this.app.roomsStore.roomExists(room.id)) { 22 | this.app.roomActionCreators.receiveRooms([room]); 23 | } 24 | } 25 | }); 26 | 27 | module.exports = ServerUpdatesSocket; -------------------------------------------------------------------------------- /app/components/newMessage.js: -------------------------------------------------------------------------------- 1 | var Marty = require('marty'); 2 | var React = require('react'); 3 | var _ = require('lodash'); 4 | 5 | var NewMessage = React.createClass({ 6 | render: function () { 7 | return ( 8 |
9 |