1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

article CRUD with socket

This commit is contained in:
Rokt33r
2015-10-16 22:12:49 +09:00
parent a1810e6023
commit 2a339a2935
12 changed files with 176 additions and 99 deletions

View File

@@ -10,6 +10,8 @@ import { findWhere, findIndex, pick } from 'lodash'
import keygen from 'boost/keygen' import keygen from 'boost/keygen'
import { NEW, refreshArticles } from './actions' import { NEW, refreshArticles } from './actions'
import api from 'boost/api' import api from 'boost/api'
import auth from 'boost/auth'
import './socket'
class HomePage extends React.Component { class HomePage extends React.Component {
componentDidMount () { componentDidMount () {
@@ -17,18 +19,18 @@ class HomePage extends React.Component {
dispatch(switchUser(this.props.params.userId)) dispatch(switchUser(this.props.params.userId))
let currentUser = JSON.parse(localStorage.getItem('currentUser')) let currentUser = auth.user()
let users = [currentUser].concat(currentUser.Teams) let users = [currentUser].concat(currentUser.Teams)
users.forEach(user => { users.forEach(user => {
api.fetchArticles(user.id) api.fetchArticles(user.id)
.then(res => { .then(res => {
dispatch(refreshArticles(user.id, res.body)) dispatch(refreshArticles(user.id, res.body))
}) })
.catch(err => { .catch(err => {
if (err.status == null) throw err if (err.status == null) throw err
console.error(err) console.error(err)
}) })
}) })
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {

View File

@@ -2,7 +2,7 @@ import React, { PropTypes } from 'react'
import ProfileImage from 'boost/components/ProfileImage' import ProfileImage from 'boost/components/ProfileImage'
import ModeIcon from 'boost/components/ModeIcon' import ModeIcon from 'boost/components/ModeIcon'
import moment from 'moment' import moment from 'moment'
import { IDLE_MODE, CREATE_MODE, EDIT_MODE, switchArticle, NEW } from '../actions' import { switchArticle, NEW } from '../actions'
export default class ArticleList extends React.Component { export default class ArticleList extends React.Component {
handleArticleClick (key) { handleArticleClick (key) {
@@ -13,16 +13,14 @@ export default class ArticleList extends React.Component {
} }
render () { render () {
let { status, articles, activeArticle } = this.props let { articles, activeArticle } = this.props
let articlesEl = articles.map(article => { let articlesEl = articles.map(article => {
let tags = Array.isArray(article.Tags) && article.Tags.length > 0 ? article.Tags.map(tag => { let tags = Array.isArray(article.Tags) && article.Tags.length > 0
return ( ? article.Tags.map(tag => {
<a key={tag.name}>{tag.name}</a> return (<a key={tag.name}>{tag.name}</a>)
) })
}) : ( : (<span>Not tagged yet</span>)
<span>Not tagged yet</span>
)
return ( return (
<div key={'article-' + article.key}> <div key={'article-' + article.key}>
@@ -53,7 +51,6 @@ export default class ArticleList extends React.Component {
} }
ArticleList.propTypes = { ArticleList.propTypes = {
status: PropTypes.shape(),
articles: PropTypes.array, articles: PropTypes.array,
activeArticle: PropTypes.shape(), activeArticle: PropTypes.shape(),
dispatch: PropTypes.func dispatch: PropTypes.func

View File

@@ -2,6 +2,7 @@ import React, { PropTypes } from 'react'
import { Link } from 'react-router' import { Link } from 'react-router'
import linkState from 'boost/linkState' import linkState from 'boost/linkState'
import { login } from 'boost/api' import { login } from 'boost/api'
import auth from 'boost/auth'
export default class LoginPage extends React.Component { export default class LoginPage extends React.Component {
constructor (props) { constructor (props) {
@@ -23,8 +24,8 @@ export default class LoginPage extends React.Component {
}, function () { }, function () {
login(this.state.user) login(this.state.user)
.then(res => { .then(res => {
localStorage.setItem('token', res.body.token) let { user, token } = res.body
localStorage.setItem('currentUser', JSON.stringify(res.body.user)) auth.user(user, token)
this.props.history.pushState('home') this.props.history.pushState('home')
}) })
@@ -34,12 +35,11 @@ export default class LoginPage extends React.Component {
return this.setState({ return this.setState({
error: { error: {
name: 'CunnectionRefused', name: 'CunnectionRefused',
message: 'Can\'t connect to API server.' message: 'Can\'t cznnect to API server.'
}, },
isSending: false isSending: false
}) })
} } else if (err.status != null) {
else if (err.status != null) {
return this.setState({ return this.setState({
error: { error: {
name: err.response.body.name, name: err.response.body.name,

View File

@@ -30,21 +30,21 @@ export function updateUser (user) {
export function refreshArticles (userId, articles) { export function refreshArticles (userId, articles) {
return { return {
type: ARTICLE_REFRESH, type: ARTICLE_REFRESH,
data: {userId, articles} data: { userId, articles }
} }
} }
export function updateArticle (userId, article) { export function updateArticle (userId, article) {
return { return {
type: ARTICLE_UPDATE, type: ARTICLE_UPDATE,
data: {userId, article} data: { userId, article }
} }
} }
export function destroyArticle (userId, articleId) { export function destroyArticle (userId, articleKey) {
return { return {
type: ARTICLE_DESTROY, type: ARTICLE_DESTROY,
data: { userId, articleId } data: { userId, articleKey }
} }
} }
@@ -70,9 +70,9 @@ export function switchMode (mode) {
} }
} }
export function switchArticle (articleId) { export function switchArticle (articleKey) {
return { return {
type: SWITCH_ARTICLE, type: SWITCH_ARTICLE,
data: articleId data: articleKey
} }
} }

View File

@@ -1,18 +1,18 @@
import React from 'react' import React from 'react'
import { createStore } from 'redux'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { updateUser, refreshArticles } from './actions' import { updateUser } from './actions'
import reducer from './reducer' import { fetchCurrentUser } from 'boost/api'
import { fetchCurrentUser, fetchArticles } from 'boost/api'
import { Router, Route, IndexRoute } from 'react-router' import { Router, Route, IndexRoute } from 'react-router'
import MainPage from './MainPage' import MainPage from './MainPage'
import LoginPage from './LoginPage' import LoginPage from './LoginPage'
import SignupPage from './SignupPage' import SignupPage from './SignupPage'
import HomePage from './HomePage' import HomePage from './HomePage'
import auth from 'boost/auth'
import store, { devToolElement } from './store'
require('../styles/main/index.styl') require('../styles/main/index.styl')
function onlyUser (state, replaceState) { function onlyUser (state, replaceState) {
var currentUser = JSON.parse(localStorage.getItem('currentUser')) let currentUser = auth.user()
if (currentUser == null) return replaceState('login', '/login') if (currentUser == null) return replaceState('login', '/login')
if (state.location.pathname === '/') return replaceState('user', '/users/' + currentUser.id) if (state.location.pathname === '/') return replaceState('user', '/users/' + currentUser.id)
} }
@@ -26,24 +26,6 @@ let routes = (
</Route> </Route>
) )
// with Dev
import { compose } from 'redux'
// Redux DevTools store enhancers
import { devTools, persistState } from 'redux-devtools'
// React components for Redux DevTools
import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react'
let finalCreateStore = compose(devTools(), persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)))(createStore)
let store = finalCreateStore(reducer)
let devEl = (
<DebugPanel top right bottom>
<DevTools store={store} monitor={LogMonitor} visibleOnLoad={false}/>
</DebugPanel>
)
// On production
// let store = createStore(reducer)
let el = document.getElementById('content') let el = document.getElementById('content')
React.render(( React.render((
@@ -51,7 +33,7 @@ React.render((
<Provider store={store}> <Provider store={store}>
{() => <Router>{routes}</Router>} {() => <Router>{routes}</Router>}
</Provider> </Provider>
{devEl} {devToolElement}
</div> </div>
), el, function () { ), el, function () {
let loadingCover = document.getElementById('loadingCover') let loadingCover = document.getElementById('loadingCover')

View File

@@ -1,17 +1,14 @@
import { combineReducers } from 'redux' import { combineReducers } from 'redux'
import { findIndex } from 'lodash' import { findIndex } from 'lodash'
import { SWITCH_USER, SWITCH_FOLDER, SWITCH_MODE, SWITCH_ARTICLE, USER_UPDATE, ARTICLE_REFRESH, ARTICLE_UPDATE, ARTICLE_DESTROY, IDLE_MODE, CREATE_MODE } from './actions' import { SWITCH_USER, SWITCH_FOLDER, SWITCH_MODE, SWITCH_ARTICLE, USER_UPDATE, ARTICLE_REFRESH, ARTICLE_UPDATE, ARTICLE_DESTROY, IDLE_MODE, CREATE_MODE } from './actions'
import auth from 'boost/auth'
const initialStatus = { const initialStatus = {
mode: IDLE_MODE mode: IDLE_MODE
} }
function getInitialCurrentUser () {
return JSON.parse(localStorage.getItem('currentUser'))
}
function getInitialArticles () { function getInitialArticles () {
let initialCurrentUser = getInitialCurrentUser() let initialCurrentUser = auth.user()
if (initialCurrentUser == null) return [] if (initialCurrentUser == null) return []
let teams = Array.isArray(initialCurrentUser.Teams) ? initialCurrentUser.Teams : [] let teams = Array.isArray(initialCurrentUser.Teams) ? initialCurrentUser.Teams : []
@@ -29,10 +26,10 @@ function currentUser (state, action) {
switch (action.type) { switch (action.type) {
case USER_UPDATE: case USER_UPDATE:
let user = action.data let user = action.data
localStorage.setItem('currentUser', JSON.stringify(user))
return user return auth.user(user)
default: default:
if (state == null) return getInitialCurrentUser() if (state == null) return auth.user()
return state return state
} }
} }
@@ -83,7 +80,7 @@ function articles (state, action) {
let teamKey = genKey(userId) let teamKey = genKey(userId)
let articles = JSON.parse(localStorage.getItem(teamKey)) let articles = JSON.parse(localStorage.getItem(teamKey))
let targetIndex = findIndex(articles, _article => article.id === _article.id) let targetIndex = findIndex(articles, _article => article.key === _article.key)
if (targetIndex < 0) articles.unshift(article) if (targetIndex < 0) articles.unshift(article)
else articles.splice(targetIndex, 1, article) else articles.splice(targetIndex, 1, article)
@@ -93,11 +90,12 @@ function articles (state, action) {
return state return state
case ARTICLE_DESTROY: case ARTICLE_DESTROY:
{ {
let { userId, articleId } = action.data let { userId, articleKey } = action.data
let teamKey = genKey(userId) let teamKey = genKey(userId)
let articles = JSON.parse(localStorage.getItem(teamKey)) let articles = JSON.parse(localStorage.getItem(teamKey))
console.log(articles)
let targetIndex = findIndex(articles, _article => articleId === _article.id) console.log(articleKey)
let targetIndex = findIndex(articles, _article => articleKey === _article.key)
if (targetIndex >= 0) articles.splice(targetIndex, 1) if (targetIndex >= 0) articles.splice(targetIndex, 1)
localStorage.setItem(teamKey, JSON.stringify(articles)) localStorage.setItem(teamKey, JSON.stringify(articles))

44
browser/main/socket.js Normal file
View File

@@ -0,0 +1,44 @@
import { API_URL } from '../../config'
import socketio from 'socket.io-client'
import auth from 'boost/auth'
import store from './store'
import { updateArticle, destroyArticle } from './actions'
export const CONN = 'CONN'
export const ALERT = 'ALERT'
export const USER_UPDATE = 'USER_UPDATE'
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
let io = socketio(API_URL)
io.on(CONN, function (data) {
console.log('connected', data)
let token = auth.token()
if (token != null) {
io.emit('JOIN', {token})
}
})
io.on(ALERT, function (data) {
console.log(ALERT, data)
})
io.on(USER_UPDATE, function (data) {
console.log(USER_UPDATE, data)
})
io.on(ARTICLE_UPDATE, function (data) {
console.log(ARTICLE_UPDATE, data)
let { userId, article } = data
store.dispatch(updateArticle(userId, article))
})
io.on(ARTICLE_DESTROY, function (data) {
console.log(ARTICLE_DESTROY, data)
let { userId, articleKey } = data
store.dispatch(destroyArticle(userId, articleKey))
})
export default io

20
browser/main/store.js Normal file
View File

@@ -0,0 +1,20 @@
import React from 'react'
import reducer from './reducer'
import { createStore } from 'redux'
// Devtools
import { compose } from 'redux'
import { devTools, persistState } from 'redux-devtools'
import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react'
let finalCreateStore = compose(devTools(), persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)))(createStore)
let store = finalCreateStore(reducer)
export let devToolElement = (
<DebugPanel top right bottom>
<DevTools store={store} monitor={LogMonitor} visibleOnLoad={false}/>
</DebugPanel>
)
// let store = createStore(reducer)
export default store

View File

@@ -38,7 +38,6 @@ noTagsColor = #999
.detailInfo .detailInfo
height 70px height 70px
width 100% width 100%
transition 0.1s
font-size 12px font-size 12px
position relative position relative
.left .left

View File

@@ -1,5 +1,2 @@
module.exports = { export const API_URL = 'http://localhost:8000/'
// apiUrl: 'https://api.b00st.io/' // export API_URL 'https://api2.b00st.io/'
// apiUrl: 'https://api2.b00st.io/'
apiUrl: 'http://localhost:8000/'
}

View File

@@ -1,107 +1,111 @@
var request = require('superagent-promise')(require('superagent'), Promise) import superagent from 'superagent'
var apiUrl = require('../config').apiUrl import superagentPromise from 'superagent-promise'
import { API_URL } from '../config'
import auth from 'boost/auth'
const request = superagentPromise(superagent, Promise)
export function login (input) { export function login (input) {
return request return request
.post(apiUrl + 'auth/login') .post(API_URL + 'auth/login')
.send(input) .send(input)
} }
export function signup (input) { export function signup (input) {
return request return request
.post(apiUrl + 'auth/register') .post(API_URL + 'auth/register')
.send(input) .send(input)
} }
export function fetchCurrentUser () { export function fetchCurrentUser () {
return request return request
.get(apiUrl + 'auth/user') .get(API_URL + 'auth/user')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
} }
export function fetchArticles (userId) { export function fetchArticles (userId) {
return request return request
.get(apiUrl + 'teams/' + userId + '/articles') .get(API_URL + 'teams/' + userId + '/articles')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
} }
export function createArticle (input) { export function createArticle (input) {
return request return request
.post(apiUrl + 'articles/') .post(API_URL + 'articles/')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
.send(input) .send(input)
} }
export function saveArticle (input) { export function saveArticle (input) {
return request return request
.put(apiUrl + 'articles/' + input.id) .put(API_URL + 'articles/' + input.id)
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
.send(input) .send(input)
} }
export function destroyArticle (articleId) { export function destroyArticle (articleId) {
return request return request
.del(apiUrl + 'articles/' + articleId) .del(API_URL + 'articles/' + articleId)
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
} }
export function createTeam (input) { export function createTeam (input) {
return request return request
.post(apiUrl + 'teams') .post(API_URL + 'teams')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
.send(input) .send(input)
} }
export function searchUser (key) { export function searchUser (key) {
return request return request
.get(apiUrl + 'search/users') .get(API_URL + 'search/users')
.query({key: key}) .query({key: key})
} }
export function setMember (teamId, input) { export function setMember (teamId, input) {
return request return request
.post(apiUrl + 'teams/' + teamId + '/members') .post(API_URL + 'teams/' + teamId + '/members')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
.send(input) .send(input)
} }
export function deleteMember (teamId, input) { export function deleteMember (teamId, input) {
return request return request
.del(apiUrl + 'teams/' + teamId + '/members') .del(API_URL + 'teams/' + teamId + '/members')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
.send(input) .send(input)
} }
export function createFolder (input) { export function createFolder (input) {
return request return request
.post(apiUrl + 'folders/') .post(API_URL + 'folders/')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
.send(input) .send(input)
} }
export function sendEmail (input) { export function sendEmail (input) {
return request return request
.post(apiUrl + 'mail') .post(API_URL + 'mail')
.set({ .set({
Authorization: 'Bearer ' + localStorage.getItem('token') Authorization: 'Bearer ' + auth.token()
}) })
.send(input) .send(input)
} }

34
lib/auth.js Normal file
View File

@@ -0,0 +1,34 @@
// initial value
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
var currentToken = localStorage.getItem('token')
function user (user, newToken) {
if (user != null) {
localStorage.setItem('currentUser', JSON.stringify(user))
currentUser = user
}
if (newToken != null) {
localStorage.setItem('token', newToken)
currentToken = newToken
}
return currentUser
}
function token () {
return currentToken
}
function clear () {
localStorage.removeItem('currentUser')
localStorage.removeItem('token')
currentUser = null
currentToken = null
}
export default {
user,
token,
clear
}