├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── client ├── app │ ├── actions │ │ └── postlistAction.js │ ├── components │ │ ├── nav │ │ │ └── nav.js │ │ └── post │ │ │ ├── post.js │ │ │ ├── postbox.js │ │ │ ├── postlist.js │ │ │ └── postmeta.js │ ├── dispatchers │ │ └── appDispatcher.js │ ├── main.js │ ├── stores │ │ └── postlistStore.js │ └── utilities │ │ └── strip.js └── public │ ├── css │ └── base.css │ └── index.html ├── docs └── setup.md ├── gulpfile.js ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── server ├── config │ └── config.json ├── data │ └── seed │ │ ├── database.js │ │ └── index.js ├── helpers │ └── logins │ │ ├── passportSerialization.js │ │ └── passportStrategy.js ├── models │ ├── index.js │ ├── post.js │ └── user.js ├── routes │ ├── api.js │ ├── logins │ │ ├── getStatus.js │ │ └── index.js │ └── posts │ │ ├── getPosts.js │ │ └── index.js └── server.js └── test ├── client └── components │ └── post │ └── postmeta.test.js ├── e2e └── home │ └── home.js └── server ├── logins └── getStatus.test.js └── posts └── getPosts.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [Makefile] 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | public/scripts/.module-cache 3 | node_modules 4 | 5 | public/npm-debug.log 6 | 7 | .idea/ 8 | 9 | *.sqlite 10 | *.log 11 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "airbnb", 3 | "esprima": "esprima-fb", 4 | "excludeFiles": ["node_modules/**"] 5 | } 6 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules/** -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": "vars", 16 | "strict": true, 17 | "predef": ["-Promise", "browser", "element", "by"], 18 | "mocha": true 19 | } 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - iojs 5 | - '0.10' 6 | - '0.12' 7 | 8 | services: 9 | - mongodb 10 | 11 | install: 12 | - npm install -g gulp 13 | - npm install 14 | - ./node_modules/protractor/bin/webdriver-manager update 15 | 16 | before_script: 17 | - npm run-script seed 18 | - export DISPLAY=:99.0 19 | - sh -e /etc/init.d/xvfb start & 20 | - npm start & 21 | - sleep 5 22 | 23 | script: 24 | - npm run-script test 25 | - npm run-script lint 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Keanux 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keanux-Personal 2 | 3 | [![Join the chat at https://gitter.im/Keanux/Keanux-Public](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Keanux/Keanux-Public?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Build Status](https://travis-ci.org/Keanux/keanux-personal.svg?branch=master)](https://travis-ci.org/Keanux/keanux-personal) 5 | [![Code Climate](https://codeclimate.com/github/Keanux/keanux-personal/badges/gpa.svg)](https://codeclimate.com/github/Keanux/keanux-personal) 6 | 7 | 這是一個一起學習 Node.js 和 React 的計畫,透過一起實作,製作個人的開源寫作平台。 8 | 9 | - [Hackpad](https://keanux.hackpad.com/INTRO-rDTHFqtALl2) 10 | - [Facebook Page](https://www.facebook.com/trykeanux) 11 | - [Keanux-Personal Demo](http://keanux.com:8080) 12 | - [Keanux](http://keanux.com) 13 | 14 | # 功能 15 | 16 | 目前僅提供顯示範例頁面,編寫及其他功能的部分正在開發,有任何錯誤或功能上的想法都歡迎在 [Issue](https://github.com/Keanux/keanux-personal/issues) 留言。 17 | 18 | # 環境安裝 19 | 20 | 參考[安裝說明](docs/setup.md)或是使用 [Keanux-Vagrant](https://github.com/Keanux/keanux-vagrant) 21 | 22 | # 快速開始 23 | 24 | 將專案 clone 至本機 25 | 26 | ``` 27 | $ git clone git@github.com:Keanux/keanux-personal.git 28 | ``` 29 | 30 | 啟動 Mongo DB 環境 31 | 32 | ``` 33 | $ mongod --dbpath <資料庫存放位置> 34 | ``` 35 | 36 | 準備好 node.js 環境,打開 Terminal 進入專案資料夾,使用以下指令安裝依賴套件並建立測試資料庫 37 | 38 | ``` 39 | $ npm install 40 | $ npm run-script seed 41 | $ npm run-script start 42 | ``` 43 | 44 | 開啟瀏覽器輸入 http://localhost:8080 45 | 46 | # 測試程式 47 | 48 | 測試Server端程式碼 49 | 50 | ``` 51 | $ gulp mocha 52 | ``` 53 | 54 | 測試Client端程式碼 55 | 56 | ``` 57 | $ gulp karma 58 | ``` 59 | 60 | 測試網站功能 (自動化測試) 61 | 62 | ``` 63 | $ ./node_modules/protractor/bin/webdriver-manager update 64 | $ npm start 65 | // 開啟另外一個視窗 66 | $ gulp e2e 67 | ``` 68 | 69 | 執行所有測試 70 | 71 | ``` 72 | $ npm test 73 | ``` 74 | 75 | # 更新到最新版本 76 | 77 | 新增遠端 Repo 網址 `upstream`,此名稱可以任意修改 78 | 79 | ``` 80 | $ git remote add upstream https://github.com/Keanux/keanux-personal.git 81 | ``` 82 | 83 | 更新遠端最新程式碼(用 merge 方式) 84 | 85 | ``` 86 | $ git pull upstream master 87 | ``` 88 | 89 | 更新遠端最新程式碼(用 rebase 方式) 90 | 91 | ``` 92 | $ git pull --rebase upstream master 93 | ``` 94 | 95 | # Copyright & License 96 | 97 | Copyright (c) 2015 Keanux - Released under the [MIT license](LICENSE). 98 | -------------------------------------------------------------------------------- /client/app/actions/postlistAction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Flux 4 | var Dispatcher = require('../dispatchers/appDispatcher'); 5 | 6 | // Actions 7 | var postlistActions = { 8 | getAllPosts: function () { 9 | Dispatcher.handleViewAction({ 10 | type: 'getAllPosts' 11 | }); 12 | } 13 | }; 14 | 15 | module.exports = postlistActions; 16 | -------------------------------------------------------------------------------- /client/app/components/nav/nav.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react/addons'); 4 | var classnames = require('classnames'); 5 | 6 | var Nav = React.createClass({ 7 | getInitialState: function () { 8 | return { 9 | hideMenu: true, 10 | isLogin: false, 11 | user: null 12 | }; 13 | }, 14 | componentDidMount: function () { 15 | var self = this; 16 | fetch('/api/logins/getStatus') 17 | .then(function (response) { 18 | return response.json(); 19 | }).then(function(result) { 20 | self.setState({ 21 | isLogin: result.isLogin, 22 | user: result.user 23 | }); 24 | }).catch(function(err) { 25 | console.log('json parsing failed', err); 26 | }); 27 | }, 28 | handleToggleMenu: function () { 29 | this.setState({ 30 | hideMenu: !this.state.hideMenu 31 | }); 32 | }, 33 | render: function () { 34 | var logoClass = classnames('nav-logo', { 35 | 'hide': !this.state.hideMenu 36 | }); 37 | var menuClass = classnames('nav-menu', { 38 | 'hide': this.state.hideMenu 39 | }); 40 | 41 | return ( 42 | 80 | ); 81 | } 82 | }); 83 | 84 | module.exports = Nav; 85 | -------------------------------------------------------------------------------- /client/app/components/post/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required Library 4 | var React = require('react'); 5 | var Showdown = require('showdown'); 6 | var Converter = new Showdown.Converter(); 7 | var Strip = require('../../utilities/strip'); 8 | 9 | // Related Control 10 | var PostMeta = require('./postmeta'); 11 | 12 | // Control 13 | var Post = React.createClass({ 14 | render: function () { 15 | var rawMarkup = Converter.makeHtml(this.props.children.toString()); 16 | return ( 17 |
18 | 19 | 20 |

21 | {this.props.title} 22 |

23 | 24 |

25 | { this.props.subtitle } 26 |

27 | 28 |
29 | { Strip(rawMarkup) } 30 |
31 |
32 | ); 33 | //
34 | } 35 | }); 36 | 37 | module.exports = Post; 38 | -------------------------------------------------------------------------------- /client/app/components/post/postbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required Library 4 | var React = require('react'); 5 | 6 | // Related Control 7 | var PostList = require('./postlist'); 8 | 9 | // Flux 10 | var PostListActions = require('../../actions/postlistAction'); 11 | var PostListStore = require('../../stores/postlistStore'); 12 | 13 | // Control 14 | var PostBox = React.createClass({ 15 | getInitialState: function () { 16 | return { 17 | postlist: PostListStore.getPosts() 18 | }; 19 | }, 20 | componentWillMount: function () { 21 | PostListStore.addChangeListener(this.onStoreChange); 22 | 23 | PostListActions.getAllPosts(); 24 | }, 25 | componentWillUnmount: function () { 26 | PostListStore.removeChangeListener(this.onStoreChange); 27 | }, 28 | onStoreChange: function () { 29 | this.setState({ 30 | postlist: PostListStore.getPosts() 31 | }); 32 | }, 33 | render: function () { 34 | return ( 35 |
36 |

Keanux-Personal Demo

37 | 38 |
39 | ); 40 | } 41 | }); 42 | 43 | module.exports = PostBox; 44 | -------------------------------------------------------------------------------- /client/app/components/post/postlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required Library 4 | var React = require('react'); 5 | 6 | // Related Control 7 | var Post = require('./post'); 8 | 9 | // Control 10 | var PostList = React.createClass({ 11 | render: function () { 12 | var postNodes = this.props.data.map(function (post, index) { 13 | return ( 14 | 20 | {post.content} 21 | 22 | ); 23 | }); 24 | return ( 25 |
26 | {postNodes} 27 |
28 | ); 29 | } 30 | }); 31 | 32 | module.exports = PostList; 33 | -------------------------------------------------------------------------------- /client/app/components/post/postmeta.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required Library 4 | var React = require('react'); 5 | var Ago = require('react-ago-component'); 6 | 7 | // Control 8 | var PostMeta = React.createClass({ 9 | render: function () { 10 | var then = new Date(this.props.createdAt); 11 | return ( 12 |
13 |
14 |
15 | 16 | 18 | 19 |
20 |
21 | {this.props.nickname} 22 | 23 | 24 | 25 |
26 |
27 |
28 | ); 29 | } 30 | }); 31 | 32 | module.exports = PostMeta; 33 | -------------------------------------------------------------------------------- /client/app/dispatchers/appDispatcher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required Library 4 | var Dispatcher = require('flux').Dispatcher; 5 | var assign = require('object-assign'); 6 | 7 | // Dispatcher 8 | var appDispatcher = assign(new Dispatcher(), { 9 | handleServerAction: function (action) { 10 | this.dispatch({ 11 | source: 'server', 12 | action: action 13 | }); 14 | }, 15 | handleViewAction: function (action) { 16 | this.dispatch({ 17 | source: 'view', 18 | action: action 19 | }); 20 | } 21 | }); 22 | 23 | module.exports = appDispatcher; 24 | -------------------------------------------------------------------------------- /client/app/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required Library 4 | var React = require('react'); 5 | 6 | // Related Control 7 | var PostBox = require('./components/post/postbox'); 8 | var Nav = require('./components/nav/nav'); 9 | 10 | // fetch polyfill 11 | if (typeof self !== 'undefined') { 12 | require('whatwg-fetch'); 13 | } 14 | 15 | React.render( 16 |
17 |
, document.getElementById('content') 20 | ); 21 | -------------------------------------------------------------------------------- /client/app/stores/postlistStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required Library 4 | var EventEmitter = require('events').EventEmitter; 5 | var assign = require('object-assign'); 6 | 7 | // Flux 8 | var Dispatcher = require('../dispatchers/appDispatcher'); 9 | 10 | // Data 11 | var postlist = []; 12 | 13 | // Store 14 | var postlistStore = assign({}, EventEmitter.prototype, { 15 | addChangeListener: function (callback) { 16 | this.on('change', callback); 17 | }, 18 | removeChangeListener: function (callback) { 19 | this.off('change', callback); 20 | }, 21 | getPosts: function () { 22 | return postlist; 23 | } 24 | }); 25 | 26 | // Watch 27 | postlistStore.dispatchToken = Dispatcher.register(function (payload) { 28 | var actions = { 29 | getAllPosts: function (payload) { 30 | fetch('/api/posts') 31 | .then(function (response) { 32 | return response.json(); 33 | }).then(function(data) { 34 | postlist = data; 35 | 36 | postlistStore.emit('change'); 37 | }).catch(function(ex) { 38 | console.log('json parsing failed', ex); 39 | }); 40 | } 41 | }; 42 | 43 | if(actions[payload.action.type]){ 44 | actions[payload.action.type](payload); 45 | } 46 | }); 47 | 48 | module.exports = postlistStore; 49 | 50 | 51 | -------------------------------------------------------------------------------- /client/app/utilities/strip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (html) { 4 | var tmp = document.createElement('DIV'); 5 | tmp.innerHTML = html; 6 | 7 | return tmp.textContent || tmp.innerText || ''; 8 | }; 9 | -------------------------------------------------------------------------------- /client/public/css/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Microsoft Jhenghei", "ff-tisa-web-pro", Georgia, Cambria, "Times New Roman", Times, serif; 3 | } 4 | 5 | a { 6 | text-decoration: none; 7 | color: #000; 8 | } 9 | 10 | h1 { 11 | font-family: Georgia, Cambria, "Times New Roman", Times, serif; 12 | font-size: 50px; 13 | text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); 14 | margin-bottom: 30px; 15 | margin-top: 12px; 16 | text-align: center; 17 | } 18 | 19 | h2 { 20 | padding: 0; 21 | margin-top: 16px; 22 | margin-bottom: 4px; 23 | } 24 | 25 | h3 { 26 | padding: 0; 27 | margin: 0; 28 | color: rgba(0, 0, 0, 0.3); 29 | font-weight: normal; 30 | } 31 | 32 | p, ul { 33 | margin: 0; 34 | } 35 | 36 | #content { 37 | width: 100%; 38 | max-width: 700px; 39 | overflow: hidden; 40 | margin: 0 auto; 41 | } 42 | 43 | img { 44 | max-width: 100%; 45 | } 46 | 47 | .show { 48 | display: block; 49 | } 50 | 51 | .hide { 52 | display: none; 53 | } 54 | 55 | /* Post */ 56 | .post { 57 | border-bottom: 1px solid #ccc; 58 | margin-bottom: 50px; 59 | padding-bottom: 50px; 60 | } 61 | 62 | .postAuthor { 63 | margin-top: 4px; 64 | } 65 | 66 | .postTitle { 67 | font-size: 42px; 68 | } 69 | 70 | .postContent { 71 | /*display: none;*/ 72 | font-size: 18px; 73 | line-height: 30px; 74 | margin-top: 30px; 75 | overflow: hidden; 76 | height: 90px; 77 | } 78 | 79 | .postMetaInline-avatar { 80 | width: 36px; 81 | height: 36px; 82 | display: none; 83 | } 84 | 85 | .avatar-image { 86 | width: 100%; 87 | height: 100%; 88 | border-radius: 100%; 89 | } 90 | 91 | .postMetaInline-feedSummary { 92 | display: table-cell; 93 | vertical-align: middle; 94 | font-size: 14px; 95 | line-height: 1.4; 96 | padding-left: 0px; 97 | } 98 | 99 | .postMetaInline--supplemental { 100 | display: block; 101 | color: rgba(0, 0, 0, 0.3); 102 | font-size: 12px; 103 | line-height: 1.1; 104 | } 105 | 106 | /* Nav */ 107 | .nav { 108 | position: absolute; 109 | top: 0; 110 | left: 0; 111 | bottom: 0; 112 | outline: 0; 113 | background-color: #232322; 114 | z-index: 300; 115 | } 116 | 117 | .nav-logo { 118 | box-sizing: initial; 119 | cursor: pointer; 120 | position: absolute; 121 | top: 10px; 122 | left: 10px; 123 | padding: 8px; 124 | height: 26px; 125 | width: 26px; 126 | z-index: 700; 127 | background-color: #333332; 128 | text-align: center; 129 | border: 0; 130 | -webkit-border-radius: 100%; 131 | -moz-border-radius: 100%; 132 | border-radius: 100%; 133 | } 134 | 135 | .nav-logo h1 { 136 | font-family: Georgia, Cambria, "Times New Roman", Times, serif; 137 | margin: 0; 138 | padding: 0; 139 | line-height: 26px; 140 | font-size: 22px; 141 | color: #FFF; 142 | } 143 | 144 | .nav-menu { 145 | text-align: left; 146 | background: #ccc; 147 | width: 280px; 148 | height: 100%; 149 | } 150 | 151 | .nav-menu-list { 152 | list-style-type: none; 153 | padding-top: 10px; 154 | padding-left: 20px; 155 | } 156 | 157 | .nav-menu-item { 158 | padding: 5px; 159 | line-height: 24px; 160 | color: #555; 161 | font-size: 14px; 162 | font-weight: bold; 163 | } 164 | 165 | .nav-menu-title { 166 | line-height: 24px; 167 | vertical-align: text-bottom; 168 | } 169 | 170 | /* Icon */ 171 | .icon { 172 | font-style: normal; 173 | display: inline-block; 174 | vertical-align: baseline; 175 | width: 24px; 176 | height: 24px; 177 | margin-right: 15px; 178 | } 179 | 180 | .icons-search { 181 | background-image: url(../img/search.png); 182 | background-size: 18px 18px !important; 183 | background-repeat: no-repeat; 184 | } 185 | 186 | .icons-keanux { 187 | color: #000; 188 | font-family: Georgia, Cambria, "Times New Roman", Times, serif; 189 | font-size: 22px; 190 | font-weight: bold; 191 | } 192 | 193 | .icons-avatar { 194 | color: #FFF; 195 | } 196 | 197 | .icons-avatar img { 198 | -webkit-border-radius: 100%; 199 | -moz-border-radius: 100%; 200 | border-radius: 100%; 201 | } 202 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Keanux-Personal Demo 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | Fork me on GitHub 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/setup.md: -------------------------------------------------------------------------------- 1 | # 環境安裝說明 2 | 3 | [Mac安裝說明](#mac安裝說明) 4 | 5 | [Ubuntu安裝說明](#ubuntu安裝說明) 6 | 7 | [Windows安裝說明](#windows安裝說明) 8 | 9 | --- 10 | 11 | 12 | ## Mac安裝說明 13 | 14 | 本說明為根據Os X 10.10.3完全乾淨的環境來設定Keanux的開發環境 15 | 16 | ### 安裝Homebrew 17 | 18 | 1. Homebrew是一套Mac專屬的套件管理工具,可以很方便的幫助我們安裝常用的工具,請使用以下指令安裝 19 | 20 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 21 | 22 | 1. 安裝完成後,可以使用以下指令檢查是否正常運作 23 | 24 | brew doctor 25 | 26 | ### 安裝Mongo DB 27 | 28 | 1. Mongo DB是一套NoSQL資料庫,我們可以透過brew來安裝MongoDB 29 | 30 | brew install mongodb 31 | 32 | 1. 啟動Mongo DB 33 | 34 | mongod --dbpath <資料庫存放位置> 35 | 36 | ### 安裝node.js (nvm) 37 | 38 | 1. 我們可以直接透過Homebrew來安裝nvm 39 | 40 | brew install nvm 41 | 42 | 1. 為了讓之後可以在Terminal快速使用nvm指令,所以我們必須把nvm的路徑加入.bash_profile之中,你可以使用以下指令快速將路徑加入.bash_profile 43 | 44 | echo "source $(brew --prefix nvm)/nvm.sh" >> .bash_profile 45 | 46 | 1. 為了讓指令馬上生效,我們重新載入.bash_profile 47 | 48 | . ~/.bash_profile 49 | 50 | 1. 安裝最新版本的node.js 51 | 52 | nvm install 0.12.4 53 | nvm use 0.12.4 54 | 55 | 1. 設定node.js default的版本 56 | 57 | nvm alias default 0.12.4 58 | nvm use default 59 | 60 | 1. 確認node.js有安裝成功 61 | 62 | node -v 63 | 64 | ### 下載Keanux程式 65 | 66 | 使用git clone到本機 67 | 68 | git clone https://github.com/Keanux/keanux-personal ~/Documents/keanux-personal 69 | 70 | ### 修改設定並啟動網站 71 | 72 | 1. 打開Terminal,進入專案所在位置 73 | 74 | cd ~/Documents/keanux-personal 75 | 76 | 1. 還原需要的package 77 | 78 | npm install 79 | 80 | 1. 建立資料表並且塞入測試資料 81 | 82 | npm run-script seed 83 | 84 | 1. 執行網站 85 | 86 | npm run-script start 87 | 88 | 1. 打開browser到http://localhost:8080,看到網站就代表成功囉! 89 | 90 | --- 91 | 92 | ## Ubuntu安裝說明 93 | 94 | 本說明為根據ubuntu 14.04完全乾淨的環境來設定Keanux的開發環境 95 | 96 | ### 安裝node.js (nvm) 97 | 98 | 1. 安裝Build所需使用的工具 99 | 100 | sudo apt-get update 101 | sudo apt-get install build-essential libssl-dev 102 | 103 | 1. 安裝nvm 104 | 105 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.3/install.sh | bash 106 | 107 | 1. 安裝完成後,重新開啟terminal就可以使用 **nvm** 指令了 108 | 109 | 1. 安裝最新版本的node.js 110 | 111 | nvm install 0.12.4 112 | nvm use 0.12.4 113 | 114 | 1. 設定node.js default的版本 115 | 116 | nvm alias default 0.12.4 117 | nvm use default 118 | 119 | 1. 確認node.js有安裝成功 120 | 121 | node -v 122 | 123 | ### 安裝Git (版本控制系統) 124 | 125 | 使用apt-get 安裝 126 | 127 | sudo apt-get install git 128 | 129 | ### 下載Keanux程式 130 | 131 | 使用git clone到本機 132 | 133 | git clone https://github.com/Keanux/keanux-personal ~/Documents/keanux-personal 134 | 135 | ### 修改設定並啟動網站 136 | 137 | 1. 打開Terminal,進入專案所在位置 138 | 139 | cd ~/Documents/keanux-personal 140 | 141 | 1. 還原需要的package 142 | 143 | npm install 144 | 145 | 1. 建立資料表並且塞入測試資料 146 | 147 | npm run-script seed 148 | 149 | 1. 執行網站 150 | 151 | npm run-script start 152 | 153 | 1. 打開browser到http://localhost:8080,看到網站就代表成功囉! 154 | 155 | --- 156 | 157 | ## Windows安裝說明 158 | 159 | 本說明為根據 Windows 8.1 的環境來設定Keanux的開發環境 160 | 161 | ### 安裝 cmder 162 | 163 | 1. 下載 [Cmder](http://gooseberrycreative.com/cmder/) 164 | 1. 安裝 Cmder Termial 165 | 166 | ### 安裝 node.js (nvm) 167 | 168 | 1. [官方下載](https://nodejs.org/) 169 | 170 | 1. 開啟 Terminal 171 | 172 | 1. 確認 node.js 有安裝成功 173 | 174 | node -v 175 | 176 | ### 安裝 Git (版本控制系統) 177 | 178 | 1. [官方下載](https://git-scm.com/download/win) 179 | 180 | ### 安裝 mongodb 181 | 182 | 1. [官方下載](https://www.mongodb.org/downloads) 183 | 184 | 1. 安裝 mongodb 185 | 186 | ### 下載 Keanux 程式 187 | 188 | 1. 開啟 Cmder Terminal 至目標目錄 (ex C:\Documents\) 189 | 190 | cd C:\Documents\ 191 | 192 | 1. 使用 git clone 到本機 193 | 194 | git clone https://github.com/Keanux/keanux-personal 195 | 196 | ### 修改設定並啟動網站 197 | 198 | 1. 打開 Terminal,進入專案所在位置 199 | 200 | cd keanux-personal 201 | 202 | 1. 還原需要的 package 203 | 204 | npm install 205 | 206 | 1. 建立資料表並且塞入測試資料 207 | 208 | npm run-script seed 209 | 210 | 1. 啟動 mongodb 211 | 212 | mongod --dbpath <資料庫存放位置> 213 | 214 | 1. 執行網站 215 | 216 | npm run-script start 217 | 218 | 1. 打開 browser 到 http://localhost:8080,看到網站就代表成功囉! 219 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var jshint = require('gulp-jshint'); 2 | var stylish = require('jshint-stylish'); 3 | var jscs = require('gulp-jscs'); 4 | var gulp = require('gulp'); 5 | var mocha = require('gulp-mocha'); 6 | var karma = require('karma').server; 7 | var protractor = require("gulp-protractor").protractor; 8 | 9 | var serverTestFiles = './test/server/**/*.js'; 10 | var clientTestFiles = './test/client/**/*.js'; 11 | var e2eTestFiles = './test/e2e/**/*.js'; 12 | var files = [ 13 | './server/**/*.js', 14 | './clinet/**/*.js', 15 | serverTestFiles, 16 | clientTestFiles, 17 | e2eTestFiles 18 | ]; 19 | 20 | // Test 21 | gulp.task('mocha', function () { 22 | return gulp.src(serverTestFiles, {read: false}) 23 | .pipe(mocha({ 24 | reporter: 'spec' 25 | })) 26 | .once('error', function () { 27 | process.exit(1); 28 | }) 29 | .once('end', function () { 30 | process.exit(); 31 | }); 32 | }); 33 | 34 | gulp.task('karma', function (done) { 35 | karma.start({ 36 | configFile: __dirname + '/karma.conf.js', 37 | singleRun: true 38 | }, done); 39 | }); 40 | 41 | gulp.task('e2e', function () { 42 | return gulp.src(e2eTestFiles) 43 | .pipe(protractor({ 44 | configFile: "protractor.conf.js", 45 | args: ['--baseUrl', 'http://localhost:8080'] 46 | })) 47 | .on('error', function (e) { 48 | throw e; 49 | }) 50 | }); 51 | 52 | // Style Check 53 | gulp.task('jshint', function () { 54 | return gulp.src(files) 55 | .pipe(jshint({linter: require('jshint-jsx').JSXHINT})) 56 | .pipe(jshint.reporter(stylish)) 57 | .pipe(jshint.reporter('fail', {verbose: true})); 58 | }); 59 | 60 | gulp.task('jscs', function () { 61 | return gulp.src(files) 62 | .pipe(jscs()); 63 | }); 64 | 65 | gulp.task('lint', ['jshint', 'jscs']); 66 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | frameworks: ['mocha'], 4 | files: [ 5 | 'test/client/**/*test.js' 6 | ], 7 | preprocessors: { 8 | 'test/client/**/*test.js': ['webpack'] 9 | }, 10 | reporters: ['progress'], 11 | browsers: ['PhantomJS'], 12 | singleRun: true, 13 | webpack: { 14 | module: { 15 | loaders: [ 16 | {test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader'} 17 | ] 18 | } 19 | } 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keanux-personal", 3 | "version": "0.0.3", 4 | "description": "Just a writing platform.", 5 | "author": "Keanux", 6 | "homepage": "http://keanux.com", 7 | "keywords": [ 8 | "keanux", 9 | "blog", 10 | "cms" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/Keanux/keanux-personal.git" 15 | }, 16 | "scripts": { 17 | "start": "node server/server.js", 18 | "seed": "node server/data/seed/index.js", 19 | "jscs": "jscs ./", 20 | "jshint": "jshint ./", 21 | "lint": "gulp lint", 22 | "test": "gulp mocha && gulp karma && gulp e2e" 23 | }, 24 | "bugs": "https://github.com/Keanux/keanux-personal/issues", 25 | "contributors": "https://github.com/Keanux/keanux-personal/graphs/contributors", 26 | "license": "MIT", 27 | "main": "server.js", 28 | "dependencies": { 29 | "bluebird": "^2.9.27", 30 | "body-parser": "~1.12.4", 31 | "classnames": "^2.1.2", 32 | "connect-browserify": "^4.0.0", 33 | "express": "~4.12.4", 34 | "express-session": "^1.11.2", 35 | "flux": "^2.0.3", 36 | "mongoose": "^4.0.5", 37 | "node-jsx": "^0.13.3", 38 | "object-assign": "^3.0.0", 39 | "passport": "^0.2.2", 40 | "passport-facebook": "^2.0.0", 41 | "react": "^0.13.3", 42 | "react-ago-component": "^0.6.1", 43 | "reactify": "^1.1.1", 44 | "showdown": "^1.0.2", 45 | "sqlite3": "^3.0.8", 46 | "whatwg-fetch": "^0.9.0" 47 | }, 48 | "devDependencies": { 49 | "babel-core": "^5.5.8", 50 | "babel-loader": "^5.1.4", 51 | "chai": "^3.0.0", 52 | "chai-datetime": "^1.4.0", 53 | "core-js": "^0.9.18", 54 | "esprima-fb": "^15001.1.0-dev-harmony-fb", 55 | "gulp": "^3.9.0", 56 | "gulp-jscs": "^1.6.0", 57 | "gulp-jshint": "^1.11.0", 58 | "gulp-mocha": "^2.1.1", 59 | "gulp-protractor": "^1.0.0", 60 | "jscs": "^1.13.1", 61 | "jshint": "^2.8.0", 62 | "jshint-jsx": "^0.4.1", 63 | "jshint-stylish": "^2.0.0", 64 | "karma": "^0.12.36", 65 | "karma-mocha": "^0.1.10", 66 | "karma-phantomjs-launcher": "^0.2.0", 67 | "karma-webpack": "^1.5.1", 68 | "mocha": "^2.2.5", 69 | "node-libs-browser": "^0.5.2", 70 | "protractor": "^2.1.0", 71 | "sinon": "^1.15.3", 72 | "webpack": "^1.9.11" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.config = { 4 | capabilities: { 5 | browserName: 'firefox' 6 | }, 7 | framework: 'mocha', 8 | mochaOpts: { 9 | timeout: 30000 // ms 10 | }, 11 | onPrepare: function(){ 12 | browser.ignoreSynchronization = true; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /server/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "dbpath":"mongodb://localhost/keanux", 4 | "facebook":{ 5 | "appId":"FACEBOOK_APP_ID", 6 | "appSecret":"FACEBOOK_APP_SECRET" 7 | } 8 | }, 9 | "test": { 10 | "dbpath":"mongodb://localhost/keanux_test", 11 | "facebook":{ 12 | "appId":"FACEBOOK_APP_ID", 13 | "appSecret":"FACEBOOK_APP_SECRET" 14 | } 15 | }, 16 | "production": { 17 | "dbpath":"mongodb://localhost/keanux", 18 | "facebook":{ 19 | "appId":"FACEBOOK_APP_ID", 20 | "appSecret":"FACEBOOK_APP_SECRET" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/data/seed/database.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Required Library 4 | var Promise = require('bluebird'); 5 | var model = require('../../models'); 6 | 7 | exports.clearAll = function() { 8 | console.log('Clear Database'); 9 | 10 | return Promise.all([ 11 | model.Post.remove(), 12 | model.User.remove() 13 | ]); 14 | }; 15 | 16 | exports.createUser = function(user) { 17 | console.log('Create User', user); 18 | 19 | return new model.User(user) 20 | .save(); 21 | }; 22 | 23 | exports.createPost = function(post) { 24 | console.log('Create Post', post); 25 | 26 | return new model.Post(post) 27 | .save(); 28 | }; 29 | -------------------------------------------------------------------------------- /server/data/seed/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('bluebird'); 4 | var database = require('./database'); 5 | 6 | database.clearAll() 7 | .then(function() { 8 | return database.createUser({ 9 | name: 'keanyc', 10 | nickname: 'KeaNy', 11 | provider: 'Facebook', 12 | loginId: '10204525184038018', 13 | photo: 'https://graph.facebook.com/keanyc/picture?width=120&height=120' 14 | }); 15 | }) 16 | .then(function(user) { 17 | return Promise.all([ 18 | database.createPost({ 19 | title: '0到1 121/365', 20 | subtitle: '凡事起頭難', 21 | content: '0到1,無中生有,在什麼也沒有的情況下,要怎麼變出東西來,靠的就是想像力,還有充分的利用現有資源。

我的工作是網站設計,主要就是必須先了解需求,了解有哪些功能,一個項目一個項目的構思大概需要的實作方法和時間,然後逐一從無到有的建構,一行一行的程式編寫出每個功能,透過軟體編譯將寫好的程式變成0與1,再進行各層次的溝通,從程式語言到機器語言再回到人機介面,一連串的拆解與組合,在一次又一次的工程專案中,我看到的是藉由持續不斷的完成0到1,將每個細節精密的打造後產出的作品,在每個項目的起頭都是最困難的開始,因為沒有一個支撐點,就是容易產生放棄的念頭,或著不知道該從哪一點下手。

也因為經歷過了無數個工作項目,才深刻的感受到,喔,其實就下手就對了,先把1完成,別想太多,做的很差也沒關係,太簡單更好,總之先求有,然後到了10的時候再回頭調整,到了20再來,30...40...80,最後終究會到100,假設100是盡頭,是完美的數字,那就再重來一次,可是這次是Level 2的1。

困難的從0到1,其實就是可以變得很簡單,別想太多,做就對了,更重要的是別一下子想的太龐大把自己都嚇傻了什麼也不敢做,真的要做,就把龐大的項目細分到馬上就能做的程度,現在就可以做,先有個開始,再來逐一評估剩下的還需要花多少時間、人力和資源。

總之,做就對了。Just do it!
', 22 | user: user 23 | }), 24 | database.createPost({ 25 | title: '思考的陷阱 122/365', 26 | subtitle: '', 27 | content: '我從很小,大概是幼稚園的時候,就開始有清醒夢的經驗,清醒夢是在睡覺做夢時,在夢的情境中發現自己在作夢,然後開始能夠任意靠著想像力造夢。清醒夢很有趣,但這篇不是要說這件事,我要說的是發現自己在作夢的這個能力,其實是個脫困的求生本能。

會這樣想也是因為我的第一個清醒夢的那一個發現的瞬間,夢到的情境是被恐龍追到黑暗的地下室,我躲起來無處可逃,因為太害怕而眼睛閉上,就在閉上後,我就從夢裡醒來發現我在作夢,這個夢在很多個晚上都發生,不知道十幾次還是幾十次後,我在夢裡就慢慢從我知道要逃到地下室,到我到了地下室後恐龍就會不見,最後變成我知道這只是夢,恐龍不是真的,然後我在夢裡把恐龍變成我的外婆。

我因為知道原來我是能夠改變夢的,慢慢我了解到現實世界也是能夠改變想法而改變對世界的看法,思考的陷阱就是像夢一樣,好像一直都在進行著,腦袋裡好多念頭跟思緒,一個人說了什麼話,發生了什麼事,我會自然的按照我的個性,我所學到的反應去反應,但當我有能力的意識到,忽然發現,這樣自然其實並不自然,其實我正在思考的陷阱中,沒有擺脫掉束縛我的慣性,以為所有人事物的存在都是不得懷疑的,無法改變的。

不是不能改變,只是還沒有意識到,我本身的意識和思想改變了,一切都會跟著改變。
', 28 | user: user 29 | }), 30 | database.createPost({ 31 | title: '問題的答案必須由自己告訴自己 123/365', 32 | subtitle: '', 33 | content: '小孩出生了,我時常在想之後她長大了,一定會像我自己小時候一樣,時常充滿著好奇心的詢問一個接著一個的問題,從這是什麼那是什麼開始,再到為什麼要這樣和那樣,然後一定會再問到我不一定聽得懂她的問題,然後我就會說這句話 -  "孩子,你必須學會開始去探索屬於自己的答案"。

印象中,我的父母親教會了我許多事情,教會了我如何游泳,教會了我如何騎車,帶著我學了大大小小的事情,但終究會到達他們的極限,或著說,我喜歡的,我走的路跟他們也完全都不一樣,慢慢我長大成年,當然我就也必須學會自己獨立思考跟解決問題。在這人生學習的過程中是永無止境的,越學也會發現自己從前的無知,而最後會開始懷疑自己是否真的明白了什麼,又真的學習到了什麼,彷彿只是冰山一角,自己也只是略懂罷了。

人生充滿著疑問,數不盡的問題人類花費若干年都持續的在探索著,從浩瀚的宇宙到極小的細胞,充滿著我們不知道的領域,或許每天都看著以為很熟悉的事物,其實都有滿滿的未知等待著我們發現,那又怎麼可能只是三言兩語可以輕易回答一些問題,這些曾經擁有的答案其實都只是其中一個可能性,而生命的可能性是無數的,自己告訴自己的答案才是最真實,因為是自己所發現到,親身體驗出來的。

保持好奇心,對於問題的答案永遠都存在好奇,是人最寶貴的資產,當人類失去了好奇心,不再探索問題,就只是像萬物一樣單純的活著,多沒意思不是嗎?

"Stay Hungry, Stay Foolish" - Steve Job
', 34 | user: user 35 | }) 36 | ]); 37 | }) 38 | .then(function() { 39 | console.log('Data seed completely.'); 40 | }) 41 | .catch(function(err) { 42 | console.log(err); 43 | }) 44 | .finally(function() { 45 | process.exit(); 46 | }); 47 | -------------------------------------------------------------------------------- /server/helpers/logins/passportSerialization.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function userSerialization(passport) { 4 | passport.serializeUser(function(user, done) { 5 | done(null, user); 6 | }); 7 | 8 | passport.deserializeUser(function(obj, done) { 9 | done(null, obj); 10 | }); 11 | 12 | } 13 | 14 | module.exports = userSerialization; 15 | -------------------------------------------------------------------------------- /server/helpers/logins/passportStrategy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../../config/config.json'); 4 | var Models = require('../../models'); 5 | var FacebookStrategy = require('passport-facebook').Strategy; 6 | 7 | var strategy = new FacebookStrategy({ 8 | clientID: config.development.facebook.appId, 9 | clientSecret: config.development.facebook.appSecret, 10 | callbackURL: 'http://localhost:8080/api/logins/facebook/callback', 11 | profileFields: ['id', 'name', 'displayName', 'photos'] 12 | }, 13 | function(accessToken, refreshToken, profile, done) { 14 | // asynchronous verification, for effect... 15 | process.nextTick(function() { 16 | Models.User 17 | .findOne({ provider: 'Facebook', loginId: profile.id }) 18 | .exec() 19 | .then(function(user) { 20 | if (user) { 21 | return done(null, user); 22 | } 23 | 24 | new Models.User({ 25 | name: profile.name.familyName + profile.name.givenName, 26 | nickname: profile.displayName, 27 | provider: 'Facebook', 28 | loginId: profile.id, 29 | photo: profile.photos[0].value 30 | }).save() 31 | .then(function(user) { 32 | return done(null, user); 33 | }); 34 | }); 35 | }); 36 | } 37 | 38 | ); 39 | 40 | module.exports = strategy; 41 | -------------------------------------------------------------------------------- /server/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Mongoose Setting 4 | var mongoose = require('mongoose'); 5 | var config = require('../config/config.json'); 6 | mongoose.connect(config.development.dbpath); 7 | 8 | if (/mongoose/.test(process.env.DEBUG)) { 9 | mongoose.set('debug', true); 10 | } 11 | 12 | exports.User = require('./user'); 13 | exports.Post = require('./post'); 14 | -------------------------------------------------------------------------------- /server/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | 6 | var postSchema = new Schema({ 7 | title: String, 8 | subtitle: String, 9 | content: String, 10 | createdAt:{ type: Date, default: Date.now }, 11 | user: { 12 | type: Schema.Types.ObjectId, 13 | ref: 'User' 14 | } 15 | }); 16 | 17 | module.exports = mongoose.model('Post', postSchema); 18 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | 6 | var userSchema = new Schema({ 7 | name: String, 8 | nickname: String, 9 | provider: String, 10 | loginId: String, 11 | photo:String 12 | }); 13 | 14 | module.exports = mongoose.model('User', userSchema); 15 | -------------------------------------------------------------------------------- /server/routes/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Express Related Library 4 | var express = require('express'); 5 | 6 | // Route 7 | var router = express.Router(); 8 | 9 | // To get all posts 10 | router.use('/posts', require('./posts')); 11 | router.use('/logins', require('./logins')); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /server/routes/logins/getStatus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function getStatus(req, res) { 4 | res.json({ 5 | isLogin: req.isAuthenticated(), 6 | user: req.user 7 | }); 8 | } 9 | 10 | module.exports = getStatus; 11 | -------------------------------------------------------------------------------- /server/routes/logins/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Express Related Library 4 | var express = require('express'); 5 | var router = express.Router(); 6 | 7 | // Passport Library 8 | var passport = require('passport'); 9 | 10 | // Serialization 11 | var serialization = require('../../helpers/logins/passportSerialization'); 12 | serialization(passport); 13 | 14 | // Strategy 15 | var strategy = require('../../helpers/logins/passportStrategy'); 16 | passport.use(strategy); 17 | 18 | // GET /logins/facebook 19 | router.get('/facebook', 20 | passport.authenticate('facebook'), 21 | function(req, res) { 22 | // The request will be redirected to Facebook for authentication, so this 23 | // function will not be called. 24 | }); 25 | 26 | // GET /logins/facebook/callback 27 | router.get('/facebook/callback', 28 | passport.authenticate('facebook', {failureRedirect: '/logins'}), 29 | function(req, res) { 30 | res.redirect('/'); 31 | }); 32 | 33 | // GET /logins/getStatus 34 | router.get('/getStatus', require('./getStatus')); 35 | 36 | module.exports = router; 37 | -------------------------------------------------------------------------------- /server/routes/posts/getPosts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Models = require('../../models'); 4 | 5 | function getPosts(req, res) { 6 | Models.Post.find({}) 7 | .populate('user') 8 | .exec() 9 | .then(function(posts) { 10 | res.json(posts); 11 | }); 12 | } 13 | 14 | module.exports = getPosts; 15 | -------------------------------------------------------------------------------- /server/routes/posts/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Express Related Library 4 | var express = require('express'); 5 | 6 | // Route 7 | var router = express.Router(); 8 | 9 | // To get all posts 10 | router.get('/', require('./getPosts')); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | // Express Related Library 6 | var express = require('express'); 7 | var session = require('express-session'); 8 | var bodyParser = require('body-parser'); 9 | var browserify = require('connect-browserify'); 10 | 11 | // Passport Library 12 | var passport = require('passport'); 13 | 14 | // React Related Library 15 | var reactify = require('reactify'); 16 | var nodeJsx = require('node-jsx'); 17 | 18 | nodeJsx.install({extension: '.jsx'}); 19 | 20 | // App middleware setting 21 | var app = express(); 22 | app.use(bodyParser.urlencoded({extended: true})); 23 | app.use(bodyParser.json()); 24 | app.use(session({secret: 'keyboard cat'})); 25 | 26 | // Initialize Passport! Also use passport.session() middleware, to support 27 | // persistent logins sessions (recommended). 28 | app.use(passport.initialize()); 29 | app.use(passport.session()); 30 | app.use(express.static(path.join(__dirname, '../client/public'))); 31 | 32 | // Register Route and Bundle.js 33 | var apiRoute = require('./routes/api'); 34 | app.use('/api', apiRoute) 35 | .use('/bundle.js', browserify.serve({ 36 | entry: path.join(__dirname, '../client/app/main'), 37 | debug: true, 38 | watch: true, 39 | transforms: [reactify] 40 | })); 41 | 42 | // Start application 43 | var port = process.env.PORT || 8080; 44 | app.listen(port); 45 | 46 | console.log('Magic happens on port ' + port); 47 | -------------------------------------------------------------------------------- /test/client/components/post/postmeta.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // ES5 shims for Function.prototype.bind, Object.prototype.keys, etc. 4 | require('core-js/es5'); 5 | 6 | // Assert Library 7 | var chai = require('chai'); 8 | var chaiDatetime = require('chai-datetime'); 9 | var expect = chai.expect; 10 | 11 | // Controls 12 | var React = require('react/addons'); 13 | var TestUtils = React.addons.TestUtils; 14 | var PostMeta = require('../../../../client/app/components/post/postmeta'); 15 | var Ago = require('react-ago-component'); 16 | 17 | // Setup 18 | chai.use(chaiDatetime); 19 | 20 | // Test 21 | describe('PostMeta', function() { 22 | it('User data display correct ', function() { 23 | var data = { 24 | username: 'user', 25 | nickname: 'nickname', 26 | createdAt: new Date() 27 | }; 28 | var postMeta = TestUtils.renderIntoDocument( 29 | 30 | ); 31 | 32 | var postMetaDOM = React.findDOMNode(postMeta); 33 | 34 | // Assert link 35 | var a = postMetaDOM.querySelectorAll('a'); 36 | expect(a[0].getAttribute('href')).to.equal('http://keanux.com/@user'); 37 | expect(a[1].getAttribute('href')).to.equal('http://keanux.com/@user'); 38 | 39 | // Assert icon 40 | var img = postMetaDOM.querySelector('img'); 41 | expect(img.getAttribute('src')).to.equal('https://graph.facebook.com/user/picture?width=120&height=120'); 42 | 43 | // Assert child control 44 | var ago = TestUtils.findRenderedComponentWithType(postMeta, Ago); 45 | expect(ago.props.date).to.equalDate(data.createdAt); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/e2e/home/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var chai = require('chai'); 4 | var expect = chai.expect; 5 | 6 | describe('HomePage', function() { 7 | beforeEach(function(done) { 8 | browser.get('/'); 9 | 10 | // Hack for wait react initial 11 | browser.sleep(1 * 1000).then(done); 12 | }); 13 | 14 | it('Title should be "Keanux-Personal Demo"', function(done) { 15 | browser.getTitle() 16 | .then(function(title) { 17 | expect(title).to.equal('Keanux-Personal Demo'); 18 | 19 | done(); 20 | }); 21 | }); 22 | 23 | it('Article\'s count should be 3', function(done) { 24 | element.all(by.css('.post')) 25 | .count() 26 | .then(function(count) { 27 | expect(count).to.equal(3); 28 | 29 | done(); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/server/logins/getStatus.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | var chai = require('chai'); 5 | var expect = chai.expect; 6 | 7 | var getStatus = require('../../../server/routes/logins/getStatus'); 8 | 9 | describe('Login', function() { 10 | describe('Get Status', function() { 11 | it('Get user status should authenticate by req.isAuthenticated', function() { 12 | // Arrange 13 | var req = {}; 14 | var res = {}; 15 | req.isAuthenticated = sinon.spy(); 16 | req.user = 'test'; 17 | res.json = sinon.spy(); 18 | 19 | // Act 20 | getStatus(req, res); 21 | 22 | // Assert 23 | expect(req.isAuthenticated.calledOnce).to.equals(true); 24 | expect(res.json.calledOnce).to.equals(true); 25 | }); 26 | 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/server/posts/getPosts.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | var chai = require('chai'); 5 | var expect = chai.expect; 6 | 7 | var mongoose = require('mongoose'); 8 | 9 | describe('Posts', function() { 10 | describe('Get Posts', function() { 11 | beforeEach(function() { 12 | sinon.stub(mongoose, 'connect'); 13 | }); 14 | 15 | afterEach(function() { 16 | mongoose.connect.restore(); 17 | }); 18 | 19 | it('Get Posts should return all posts', function() { 20 | // Arrange 21 | var req = {}; 22 | var res = {}; 23 | res.json = sinon.spy(); 24 | 25 | var post = { 26 | title: '0到1 121/365', 27 | subtitle: '凡事起頭難', 28 | content: '0到1,無中生有,在什麼也沒有的情況下,要怎麼變出東西來,靠的就是想像力,還有充分的利用現有資源。

我的工作是網站設計,主要就是必須先了解需求,了解有哪些功能,一個項目一個項目的構思大概需要的實作方法和時間,然後逐一從無到有的建構,一行一行的程式編寫出每個功能,透過軟體編譯將寫好的程式變成0與1,再進行各層次的溝通,從程式語言到機器語言再回到人機介面,一連串的拆解與組合,在一次又一次的工程專案中,我看到的是藉由持續不斷的完成0到1,將每個細節精密的打造後產出的作品,在每個項目的起頭都是最困難的開始,因為沒有一個支撐點,就是容易產生放棄的念頭,或著不知道該從哪一點下手。

也因為經歷過了無數個工作項目,才深刻的感受到,喔,其實就下手就對了,先把1完成,別想太多,做的很差也沒關係,太簡單更好,總之先求有,然後到了10的時候再回頭調整,到了20再來,30...40...80,最後終究會到100,假設100是盡頭,是完美的數字,那就再重來一次,可是這次是Level 2的1。

困難的從0到1,其實就是可以變得很簡單,別想太多,做就對了,更重要的是別一下子想的太龐大把自己都嚇傻了什麼也不敢做,真的要做,就把龐大的項目細分到馬上就能做的程度,現在就可以做,先有個開始,再來逐一評估剩下的還需要花多少時間、人力和資源。

總之,做就對了。Just do it!
', 29 | user: { name: 'test' } 30 | }; 31 | var mockFind = { 32 | populate: function(table) { 33 | return this; 34 | }, 35 | 36 | exec: function() { 37 | return this; 38 | }, 39 | 40 | then: function(callback) { 41 | callback([post]); 42 | } 43 | }; 44 | 45 | var Models = require('../../../server/models'); 46 | sinon.stub(Models.Post, 'find') 47 | .withArgs({}) 48 | .returns(mockFind); 49 | 50 | // Act 51 | var getPosts = require('../../../server/routes/posts/getPosts'); 52 | getPosts(req, res); 53 | 54 | // Assert 55 | expect(res.json.calledOnce).to.equals(true); 56 | expect(Models.Post.find.calledOnce).to.equals(true); 57 | }); 58 | }); 59 | }); 60 | --------------------------------------------------------------------------------