├── .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 | [](https://gitter.im/Keanux/Keanux-Public?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 | [](https://travis-ci.org/Keanux/keanux-personal)
5 | [](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 |
47 |
79 |
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 |
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 |
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 | , 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 |
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 |
--------------------------------------------------------------------------------