├── .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 |
17 | );
18 | },
19 | getInitialState: function () {
20 | return {
21 | text: ''
22 | };
23 | },
24 | onKeyDown: function (e) {
25 | if (e.keyCode === 13) {
26 | this.sendMessage();
27 | }
28 | },
29 | updateText: function (e) {
30 | this.setState({
31 | text: e.currentTarget.value
32 | });
33 | },
34 | sendMessage: function () {
35 | this.app.messageActionCreators.sendMessage(
36 | this.state.text,
37 | this.props.roomId
38 | );
39 | this.setState(this.getInitialState());
40 | return false;
41 | }
42 | });
43 |
44 | module.exports = Marty.createContainer(NewMessage);
--------------------------------------------------------------------------------
/app/components/home.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var React = require('react');
3 | var Marty = require('marty');
4 | var NewRoom = require('./newRoom');
5 |
6 | var Home = React.createClass({
7 | render() {
8 | return (
9 |
10 |
11 |
12 | {_.map(this.props.rooms, (room) => {
13 | return (
14 | -
15 |
17 | {room.name}
18 |
19 |
20 | );
21 | })}
22 |
23 |
24 | );
25 | },
26 | navigateToRoom(roomId) {
27 | this.app.navigationActionCreators.navigateToRoom(roomId);
28 | }
29 | });
30 |
31 | module.exports = Marty.createContainer(Home, {
32 | listenTo: 'roomsStore',
33 | fetch: {
34 | rooms() {
35 | return this.app.roomsStore.getAll();
36 | }
37 | },
38 | pending() {
39 | return Loading rooms...
;
40 | },
41 | failed(errors) {
42 | return Failed to load rooms. {errors}
;
43 | }
44 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "marty-chat-example",
3 | "version": "0.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "babel": "^5.4.7",
7 | "eventemitter2": "^0.4.14",
8 | "express": "^4.12.4",
9 | "lodash": "^3.9.3",
10 | "marty": "^0.10.1",
11 | "marty-express": "^0.10.0",
12 | "marty-socket.io-state-source": "^0.1.0",
13 | "react": "^0.13.0",
14 | "react-router": "^0.13.0",
15 | "socket.io": "^1.1.0",
16 | "super-simple-db": "^1.0.0",
17 | "uuid": "^2.0.1"
18 | },
19 | "scripts": {
20 | "start": "grunt",
21 | "test": "grunt test"
22 | },
23 | "browserify": {
24 | "transform": [
25 | "babelify",
26 | "envify",
27 | "cssify"
28 | ]
29 | },
30 | "devDependencies": {
31 | "aliasify": "^1.5.1",
32 | "babelify": "^6.0.0",
33 | "body-parser": "^1.9.0",
34 | "browserify": "^10.2.3",
35 | "browserify-shim": "^3.8.7",
36 | "cli-table": "^0.3.1",
37 | "cookie-parser": "^1.3.5",
38 | "cssify": "^0.7.0",
39 | "ejs": "^2.3.1",
40 | "envify": "^3.2.0",
41 | "grunt": "^0.4.5",
42 | "grunt-browserify": "^3.2.1",
43 | "grunt-cli": "^0.1.13",
44 | "grunt-concurrent": "^1.0.0",
45 | "grunt-contrib-uglify": "^0.9.1",
46 | "grunt-contrib-watch": "^0.6.1",
47 | "grunt-exorcise": "^1.0.1",
48 | "grunt-nodemon": "^0.4.0",
49 | "load-grunt-tasks": "3.2.0",
50 | "morgan": "~1.5.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/components/newRoom.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var Marty = require('marty');
3 |
4 | var NewRoom = React.createClass({
5 | render: function () {
6 | var name = this.state.name;
7 |
8 | return (
9 |
25 | );
26 | },
27 | getInitialState: function () {
28 | return {
29 | name: ''
30 | }
31 | },
32 | onKeyDown: function (e) {
33 | if (e.keyCode === 13) {
34 | this.createRoom();
35 | }
36 | },
37 | updateRoomName: function (e) {
38 | this.setState({
39 | name: e.currentTarget.value
40 | });
41 | },
42 | createRoom: function (e) {
43 | e.stopPropagation();
44 | e.preventDefault();
45 |
46 | if (this.state.name.trim() !== "") {
47 | this.app.roomActionCreators.createRoom(this.state.name);
48 | this.setState({
49 | name: ''
50 | });
51 | }
52 | }
53 | });
54 |
55 | module.exports = Marty.createContainer(NewRoom);
--------------------------------------------------------------------------------
/app/server/rooms.js:
--------------------------------------------------------------------------------
1 | var db = require('./db');
2 | var _ = require('lodash');
3 | var util = require('util');
4 | var uuid = require('uuid').v1;
5 | var EventEmitter2 = require('eventemitter2').EventEmitter2;
6 |
7 | function Rooms() {
8 | this.getRoom = getRoom;
9 | this.addMessage = addMessage;
10 | this.createRoom = createRoom;
11 | this.getAllRooms = getAllRooms;
12 | this.getRoomMessages = getRoomMessages;
13 | this.state = {};
14 |
15 | EventEmitter2.call(this, {
16 | wildcard: true
17 | });
18 |
19 | var emit = _.bind(this.emit, this);
20 |
21 | function createRoom(room) {
22 | _.defaults(room, {
23 | id: uuid(),
24 | messages: []
25 | });
26 |
27 | db.set(room.id, room);
28 | emit('room:created', room);
29 |
30 | return room;
31 | }
32 |
33 | function getRoom(roomId) {
34 | var room = db.get(roomId);
35 |
36 | if (room) {
37 | return room;
38 | }
39 | }
40 |
41 | function getRoomMessages(roomId) {
42 | var room = db.get(roomId);
43 |
44 | if (room) {
45 | return room.messages;
46 | }
47 |
48 | return [];
49 | }
50 |
51 | function getAllRooms() {
52 | return _.values(db.get());
53 | }
54 |
55 | function addMessage(roomId, message) {
56 | var room = db.get(roomId);
57 |
58 | if (room) {
59 | _.defaults(message, {
60 | id: uuid()
61 | });
62 |
63 | room.messages.push(message);
64 |
65 | db.set(roomId, room);
66 |
67 | emit('message', message);
68 |
69 | return message;
70 | }
71 | }
72 | }
73 |
74 | util.inherits(Rooms, EventEmitter2);
75 |
76 | module.exports = new Rooms();
--------------------------------------------------------------------------------
/app/components/room.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var Marty = require('marty');
3 | var _ = require('lodash');
4 | var NewMessage = require('./newMessage');
5 | var RoomsStore = require('../stores/roomsStore');
6 | var MessagesStore = require('../stores/messagesStore');
7 |
8 | var Room = React.createClass({
9 | render: function () {
10 | var room = this.props.room;
11 | var messages = _.sortBy(this.props.messages, function (message) {
12 | return new Date(message.timestamp);
13 | });
14 |
15 | return (
16 |
17 |
18 |
{room.name}
19 |
20 | {_.map(messages, (message) => {
21 | return (
22 | -
23 |
24 | {message.text}
25 |
26 |
27 | );
28 | })}
29 |
30 |
31 |
32 |
33 | );
34 | }
35 | });
36 |
37 | module.exports = Marty.createContainer(Room, {
38 | listenTo: ['roomsStore', 'messagesStore'],
39 | fetch: {
40 | room() {
41 | return this.app.roomsStore.getRoom(this.props.id)
42 | },
43 | messages() {
44 | return this.app.messagesStore.getMessagesForRoom(this.props.id)
45 | }
46 | },
47 | pending() {
48 | return Loading...
;
49 | },
50 | failed(errors) {
51 | return Failed to load room. {errors}
;
52 | }
53 | });
--------------------------------------------------------------------------------
/app/stores/messagesStore.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var Marty = require('marty');
3 | var MessageUtils = require('../utils/messageUtils');
4 | var MessageQueries = require('../queries/messageQueries');
5 | var MessageConstants = require('../constants/messageConstants');
6 |
7 | var MessagesStore = Marty.createStore({
8 | id: 'Messages',
9 | handlers: {
10 | addMessages: MessageConstants.RECEIVE_MESSAGES,
11 | updateMessage: MessageConstants.UPDATE_MESSAGE
12 | },
13 | getInitialState: function () {
14 | return {};
15 | },
16 | getMessagesForRoom: function (roomId) {
17 | return this.fetch({
18 | id: roomId,
19 | locally: function () {
20 | return this.state[roomId];
21 | },
22 | remotely: function () {
23 | return this.app.messageQueries.getMessagesForRoom(roomId);
24 | }
25 | });
26 | },
27 | getMessage: function (messageId, roomId) {
28 | var messages = this.state[roomId];
29 |
30 | if (messages) {
31 | return _.findWhere(messages, {
32 | id: messageId
33 | });
34 | }
35 | },
36 | updateMessage: function (cid, message) {
37 | var oldMessage = _.findWhere(this.state[message.roomId], {
38 | cid: cid
39 | });
40 |
41 | if (oldMessage) {
42 | _.extend(oldMessage, message);
43 | this.hasChanged();
44 | }
45 | },
46 | addMessages: function (roomId, messages) {
47 | if (!_.isArray(messages)) {
48 | messages = [messages];
49 | }
50 |
51 | _.each(messages, function (message) {
52 | if (!message.cid) {
53 | message.cid = MessageUtils.cid();
54 | }
55 | }, this);
56 |
57 | this.state[roomId] = _.union(messages, this.state[roomId]);
58 | this.hasChanged();
59 | }
60 | });
61 |
62 | module.exports = MessagesStore;
--------------------------------------------------------------------------------
/app/stores/roomsStore.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | var Marty = require('marty');
3 | var RoomUtils = require('../utils/roomUtils');
4 | var RoomQueries = require('../queries/roomQueries');
5 | var RoomConstants = require('../constants/roomConstants');
6 |
7 | var RoomStore = Marty.createStore({
8 | id: 'Rooms',
9 | handlers: {
10 | updateRoom: RoomConstants.UPDATE_ROOM,
11 | addRooms: RoomConstants.RECEIVE_ROOMS
12 | },
13 | getInitialState: function () {
14 | return {};
15 | },
16 | getAll: function () {
17 | return this.fetch({
18 | id: 'all-rooms',
19 | locally: function () {
20 | if (this.hasAlreadyFetched('all-rooms')) {
21 | return _.values(this.state);
22 | }
23 | },
24 | remotely: function () {
25 | return this.app.roomQueries.getAllRooms();
26 | }
27 | });
28 | },
29 | getRoom: function (id) {
30 | return this.fetch({
31 | id: id,
32 | dependsOn: this.getAll(),
33 | locally: function () {
34 | return _.findWhere(_.values(this.state), {
35 | id: id
36 | }) || null;
37 | }
38 | });
39 | },
40 | roomExists: function (id) {
41 | return _.findWhere(_.values(this.state), {
42 | id: id
43 | });
44 | },
45 | updateRoom: function (cid, room) {
46 | this.state[cid] = _.extend(room, this.state[cid]);
47 | this.hasChanged();
48 | },
49 | addRoom: function (room) {
50 | this.addRooms([room]);
51 | },
52 | addRooms: function (rooms) {
53 | if (!_.isArray(rooms)) {
54 | rooms = [rooms];
55 | }
56 |
57 | _.each(rooms, function (room) {
58 | if (!room.cid) {
59 | room.cid = RoomUtils.cid();
60 | }
61 |
62 | this.state[room.cid] = room;
63 | }, this);
64 |
65 | this.hasChanged();
66 | }
67 | });
68 |
69 | module.exports = RoomStore;
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 | require('load-grunt-tasks')(grunt)
3 |
4 | var INPUT_PATH = 'app/index.js';
5 | var OUTPUT_PATH = './dist/javascripts/marty-chat-example.js';
6 |
7 | grunt.registerTask('default', 'concurrent:serve');
8 | grunt.registerTask('release', ['browserify:release', 'exorcise', 'uglify:release']);
9 |
10 | grunt.initConfig({
11 | nodemon: {
12 | serve: {
13 | script: './app/server'
14 | }
15 | },
16 | concurrent: {
17 | serve: {
18 | tasks: ['browserify:watch', 'nodemon:serve'],
19 | options: {
20 | logConcurrentOutput: true
21 | }
22 | }
23 | },
24 | browserify: {
25 | release: browserifyOptions({
26 | debug: true
27 | }),
28 | watch: browserifyOptions({
29 | watch: true,
30 | debug: true
31 | }),
32 | },
33 | exorcise: {
34 | bundle: {
35 | files: {
36 | './dist/javascripts/marty-chat-example.map': [OUTPUT_PATH]
37 | }
38 | }
39 | },
40 | uglify: {
41 | release: {
42 | options: {
43 | sourceMap: true,
44 | sourceMapIncludeSources: true,
45 | sourceMapIn: 'dist/javascripts/marty-chat-example.map'
46 | },
47 | files: {
48 | 'dist/javascripts/marty-chat-example.min.js': ['dist/javascripts/marty-chat-example.js']
49 | }
50 | }
51 | }
52 | });
53 |
54 | function browserifyOptions(options) {
55 | options || (options = {});
56 |
57 | return {
58 | src: [INPUT_PATH],
59 | dest: OUTPUT_PATH,
60 | options: {
61 | watch: !!options.watch,
62 | keepAlive: !!options.watch,
63 | transform: ['babelify', 'envify', 'cssify'],
64 | browserifyOptions: {
65 | debug: !!options.debug
66 | }
67 | }
68 | };
69 | }
70 | };
--------------------------------------------------------------------------------
/app/server/index.js:
--------------------------------------------------------------------------------
1 | require('babel/register');
2 |
3 | var fs = require('fs');
4 | var _ = require('lodash');
5 | var util = require('util');
6 | var path = require('path');
7 | var uuid = require('uuid').v1;
8 | var morgan = require('morgan');
9 | var rooms = require('./rooms');
10 | var express = require('express');
11 | var Table = require('cli-table');
12 | var bodyParser = require('body-parser');
13 |
14 | var app = express();
15 | var port = process.env.PORT || 5000;
16 | var server = require('http').Server(app);
17 | var io = require('socket.io')(server);
18 |
19 | console.log('Running server http://localhost:' + port);
20 | server.listen(port);
21 |
22 | app.set('views', path.join(__dirname, 'views'));
23 | app.set('view engine', 'ejs');
24 | app.set('port', process.env.PORT || 5000);
25 |
26 | app.use(morgan('dev'));
27 | app.use(bodyParser.json());
28 | app.use(require('marty-express')({
29 | routes: require('../routes'),
30 | application: require('../application'),
31 | rendered: function (result) {
32 | console.log('Rendered ' + result.req.url);
33 |
34 | var table = new Table({
35 | colWidths: [30, 30, 30, 30, 40],
36 | head: ['Store Id', 'Fetch Id', 'Status', 'Time', 'Result']
37 | });
38 |
39 | _.each(result.diagnostics, function (diagnostic) {
40 | table.push([
41 | diagnostic.storeId,
42 | diagnostic.fetchId,
43 | diagnostic.status,
44 | diagnostic.time,
45 | JSON.stringify(diagnostic.result || diagnostic.error || {}, null, 2)
46 | ]);
47 | });
48 |
49 | console.log(table.toString());
50 | }
51 | }));
52 |
53 | app.use(express.static(path.join(__dirname, '..', '..', 'dist')));
54 | app.use('/styles', express.static(path.join(__dirname, '..', 'styles')));
55 | app.use('/node_modules', express.static(path.join(__dirname, '..', '..', 'node_modules')));
56 |
57 | app.get('/rooms/:id', function (req, res) {
58 | res.render('index');
59 | });
60 |
61 | app.get('/api/rooms', function(req, res) {
62 | res.json(rooms.getAllRooms()).end();
63 | });
64 |
65 | app.post('/api/rooms', function (req, res) {
66 | var room = rooms.createRoom(req.body);
67 |
68 | res.json(room).status(201).end();
69 | });
70 |
71 | app.post('/api/rooms/:roomId/messages', function(req, res) {
72 | var message = rooms.addMessage(roomId(req), req.body);
73 |
74 | res.json(message).status(201).end();
75 | });
76 |
77 | app.get('/api/rooms/:roomId/messages', function (req, res) {
78 | var messages = rooms.getRoomMessages(roomId(req));
79 |
80 | res.json(messages).end();
81 | });
82 |
83 | rooms.on('*', function() {
84 | console.log.apply(console, _.union([this.event], _.toArray(arguments)));
85 | });
86 |
87 | io.on('connection', function(socket) {
88 | rooms.on('*', function() {
89 | var args = _.toArray(arguments);
90 | args.unshift(this.event);
91 | socket.emit.apply(socket, args);
92 | });
93 | });
94 |
95 | function roomId(req) {
96 | return req.params.roomId;
97 | }
--------------------------------------------------------------------------------