├── .gitattributes ├── .gitignore ├── .gitmodules ├── .nvmrc ├── LICENSE ├── README.md ├── conf.js ├── design ├── github-corner.svg └── web.sketch ├── doc └── dashboard-screenshot.png ├── docker-compose.yml ├── knexfile.js ├── migrations ├── 20160117222542_add_users_table.js ├── 20160131195215_add_repos_table.js ├── 20160131203758_add_tags_table.js ├── 20160131204053_add_repo_tags_table.js ├── 20160218195519_add_user_displayname_and_avatar.js ├── 20160220132046_add_uniq_constraints_to_users.js ├── 20160220212753_add_user_id_to_repo_tags_table.js └── 20160321225635_auto_add_created_at_and_updated_at.js ├── package.json ├── scripts ├── bootstrap ├── cibuild ├── console ├── dev ├── prod-test-timing ├── server ├── setup ├── test └── update └── workspace.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sketch filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /_build-tmp 3 | node_modules 4 | /public 5 | /build 6 | local* 7 | *.log 8 | /secret 9 | /deployment 10 | /typings/main* 11 | /typings/browser* 12 | /.idea 13 | .tmp 14 | .npmrc 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "http-server"] 2 | path = http-server 3 | url = git@github.com:TryStarboard/http-server.git 4 | [submodule "job-server"] 5 | path = job-server 6 | url = git@github.com:TryStarboard/job-server.git 7 | [submodule "starboard-ui"] 8 | path = starboard-ui 9 | url = git@github.com:TryStarboard/starboard-ui.git 10 | [submodule "websocket-server"] 11 | path = websocket-server 12 | url = git@github.com:TryStarboard/websocket-server.git 13 | [submodule "redis"] 14 | path = redis 15 | url = git@github.com:TryStarboard/redis.git 16 | [submodule "log"] 17 | path = log 18 | url = git@github.com:TryStarboard/log.git 19 | [submodule "models"] 20 | path = models 21 | url = git@github.com:TryStarboard/models.git 22 | [submodule "github"] 23 | path = github 24 | url = git@github.com:TryStarboard/github.git 25 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6.3.1 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Starboard 2 | 3 | A github starred repo management app. 4 | 5 | Video demo https://vimeo.com/163324105. Sorry for not creating a tutorial UI in the app :P 6 | 7 | ![](./doc/dashboard-screenshot.png) 8 | 9 | ## Acknowledgement 10 | 11 | Thanks [Eldar Burnashev (@frontendjedi)](https://twitter.com/frontendjedi) for designing the dashboard UI. 12 | 13 | ## Tech Stack 14 | 15 | - Database 16 | - Postgres 9.5 (primary datastore) 17 | - Redis 3.0 (session store) 18 | - Server 19 | - Node.js 20 | - Koa (http server) 21 | - React (server rendering) 22 | - WebSocket 23 | - Browser 24 | - React 25 | - React Router 26 | - Redux 27 | - WebSocket 28 | - Support 29 | - Docker 30 | - Webpack 31 | - Babel 32 | - Eslint 33 | - Deployment 34 | - Kubernetes 35 | 36 | ## Development Setup 37 | 38 | ### Docker 39 | 40 | You will need Docker to run databases. You can get Docker in your machine anyway you want if you know what you are doing. If not, the most recommended way to get Docker is through homebrew (assuming you are using OS X, sorry Windows folks). 41 | 42 | ```sh 43 | # Run in a shell 44 | brew update 45 | brew install docker-machine docker-compose 46 | docker-machine create -d virtualbox starboard-dev 47 | ``` 48 | 49 | After installation of Docker tools, each time you want to work on this project, you have to running following steps to bring Docker server online: 50 | 51 | ```sh 52 | docker-machine start starboard-dev 53 | eval $(docker-machine env starboard-dev) 54 | ``` 55 | 56 | ### Node.js 57 | 58 | The backend of this application is entirely based on JavaScript. The server is in Node.js. Currently it works on 59 | 60 | - node v5.5.0 61 | - npm v3.5.3 62 | 63 | Don't forget to run `npm install` to install all the dependencies. 64 | 65 | ### Environment Variables 66 | 67 | Make sure you have `./node_modules/.bin` in your `$PATH` so you can use local installed executables. 68 | 69 | ### Configuration Files 70 | 71 | You will need to create a `config/local-development.js` that looks like: 72 | 73 | ```js 74 | 'use strict'; 75 | 76 | module.exports = { 77 | github: { 78 | clientID: '', 79 | clientSecret: '', 80 | } 81 | }; 82 | ``` 83 | 84 | You will have to go to https://github.com/settings/developers to create a developer application and put the client ID and client secret here. 85 | 86 | ### Database Schema 87 | 88 | First, you have to ensure databases are running. (Make sure you have seen the [Docker section](#docker).) 89 | 90 | _It might take some time to run for the first time, since Docker has to pull down all the images for the first time._ 91 | 92 | ```sh 93 | npm run dev:databases 94 | ``` 95 | 96 | Then, in another shell session, run following commands to migrate database to latest schema 97 | 98 | ```sh 99 | # In shell 100 | knex migrate:latest 101 | ``` 102 | 103 | You can stop databases after migration finished. 104 | 105 | ### Start Hacking 106 | 107 | If you successfully reached here without any problem. Congratulation you might have completed a complicated dev setup. But I assure you those will never get in your way again. The last thing you need to run to start the dev server is: 108 | 109 | ```sh 110 | # In shell 111 | npm run dev 112 | ``` 113 | -------------------------------------------------------------------------------- /conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const convict = require('convict'); 4 | 5 | const conf = convict({ 6 | postgres: { 7 | host: { 8 | env: 'POSTGRES_HOST', 9 | default: null, 10 | format: mustDefine, 11 | }, 12 | database: { 13 | env: 'POSTGRES_DB', 14 | default: null, 15 | format: mustDefine, 16 | }, 17 | user: { 18 | env: 'POSTGRES_USER', 19 | default: null, 20 | format: mustDefine, 21 | }, 22 | password: { 23 | env: 'POSTGRES_PASS', 24 | default: null, 25 | format: mustDefine, 26 | }, 27 | port: { 28 | env: 'POSTGRES_PORT', 29 | default: null, 30 | format: mustDefine, 31 | }, 32 | }, 33 | }); 34 | 35 | conf.validate({strict: true}); 36 | 37 | module.exports = conf; 38 | 39 | function mustDefine(val) { 40 | if (!val) { 41 | throw new Error('must be defined'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /design/github-corner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /design/web.sketch: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:aa178473ea2653594a23723098e65123cfddd8e8cfae03b391cc86befb9445dd 3 | size 720896 4 | -------------------------------------------------------------------------------- /doc/dashboard-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryStarboard/Starboard/7c3dbcd999bebe15eac888f1873cfa1accdeb905/doc/dashboard-screenshot.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | postgres: 2 | image: postgres:9.5.3 3 | ports: 4 | - '10101:5432' 5 | environment: 6 | - POSTGRES_USER=starboard-dev 7 | - POSTGRES_DB=starboard-dev 8 | 9 | redis: 10 | image: redis:3.0 11 | ports: 12 | - '10102:6379' 13 | -------------------------------------------------------------------------------- /knexfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const conf = require('./conf'); 4 | 5 | module.exports = { 6 | [process.env.NODE_ENV || 'development']: { 7 | client: 'pg', 8 | connection: conf.get('postgres'), 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /migrations/20160117222542_add_users_table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.up = (knex, Promise) => { 4 | return knex.schema.createTable('users', (table) => { 5 | table.increments(); 6 | table.bigInteger('github_id'); 7 | table.string('email'); 8 | table.string('username'); 9 | table.string('access_token'); 10 | table.string('refresh_token'); 11 | table.timestamps(); 12 | }); 13 | }; 14 | 15 | exports.down = (knex, Promise) => { 16 | return knex.schema.dropTable('users'); 17 | }; 18 | -------------------------------------------------------------------------------- /migrations/20160131195215_add_repos_table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.up = (knex, Promise) => { 4 | return knex.schema.createTable('repos', (table) => { 5 | table.increments(); 6 | table.integer('user_id'); 7 | table.bigInteger('github_id'); 8 | table.string('full_name'); 9 | table.text('description'); 10 | table.string('homepage'); 11 | table.string('html_url'); 12 | table.integer('forks_count'); 13 | table.integer('stargazers_count'); 14 | table.timestamp('starred_at'); 15 | table.timestamps(); 16 | 17 | table.unique(['user_id', 'github_id']); 18 | }); 19 | }; 20 | 21 | exports.down = (knex, Promise) => { 22 | return knex.schema.dropTable('repos'); 23 | }; 24 | -------------------------------------------------------------------------------- /migrations/20160131203758_add_tags_table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.up = (knex, Promise) => { 4 | return knex.schema.createTable('tags', (table) => { 5 | table.increments(); 6 | table.integer('user_id'); 7 | table.string('text'); 8 | table.string('foreground_color'); 9 | table.string('background_color'); 10 | table.timestamps(); 11 | 12 | table.unique(['user_id', 'text']); 13 | }); 14 | }; 15 | 16 | exports.down = (knex, Promise) => { 17 | return knex.schema.dropTable('tags'); 18 | }; 19 | -------------------------------------------------------------------------------- /migrations/20160131204053_add_repo_tags_table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.up = (knex, Promise) => { 4 | return knex.schema.createTable('repo_tags', (table) => { 5 | table.increments(); 6 | table.integer('tag_id'); 7 | table.integer('repo_id'); 8 | table.timestamps(); 9 | 10 | table.unique(['tag_id', 'repo_id']); 11 | }); 12 | }; 13 | 14 | exports.down = (knex, Promise) => { 15 | return knex.schema.dropTable('repo_tags'); 16 | }; 17 | -------------------------------------------------------------------------------- /migrations/20160218195519_add_user_displayname_and_avatar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.up = function (knex, Promise) { 4 | return knex.schema.table('users', (table) => { 5 | table.string('displayname'); 6 | table.string('avatar'); 7 | }); 8 | }; 9 | 10 | exports.down = function (knex, Promise) { 11 | return knex.schema.table('users', (table) => { 12 | table.dropColumn('displayname'); 13 | table.dropColumn('avatar'); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /migrations/20160220132046_add_uniq_constraints_to_users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.up = (knex, Promise) => { 4 | return knex.schema.table('users', function (table) { 5 | table.unique(['github_id']); 6 | }); 7 | }; 8 | 9 | exports.down = (knex, Promise) => { 10 | return knex.schema.table('users', function (table) { 11 | table.dropUnique(['github_id']); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20160220212753_add_user_id_to_repo_tags_table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.up = function (knex, Promise) { 4 | return knex.schema.table('repo_tags', (table) => { 5 | table.string('user_id'); 6 | }); 7 | }; 8 | 9 | exports.down = function (knex, Promise) { 10 | return knex.schema.table('repo_tags', (table) => { 11 | table.dropColumn('user_id'); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /migrations/20160321225635_auto_add_created_at_and_updated_at.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.up = (knex, Promise) => { 4 | return Promise.all([ 5 | knex.schema.raw('ALTER TABLE users ALTER created_at SET DEFAULT current_timestamp'), 6 | knex.schema.raw('ALTER TABLE repos ALTER created_at SET DEFAULT current_timestamp'), 7 | knex.schema.raw('ALTER TABLE tags ALTER created_at SET DEFAULT current_timestamp'), 8 | knex.schema.raw('ALTER TABLE repo_tags ALTER created_at SET DEFAULT current_timestamp'), 9 | 10 | knex.schema.raw('ALTER TABLE users ALTER updated_at SET DEFAULT current_timestamp'), 11 | knex.schema.raw('ALTER TABLE repos ALTER updated_at SET DEFAULT current_timestamp'), 12 | knex.schema.raw('ALTER TABLE tags ALTER updated_at SET DEFAULT current_timestamp'), 13 | knex.schema.raw('ALTER TABLE repo_tags ALTER updated_at SET DEFAULT current_timestamp'), 14 | 15 | knex.schema.raw(`CREATE FUNCTION update_at_column() 16 | RETURNS TRIGGER AS $$ 17 | BEGIN 18 | NEW.updated_at = now(); 19 | RETURN NEW; 20 | END; 21 | $$ language 'plpgsql';`), 22 | 23 | knex.schema.raw(`CREATE TRIGGER update_user_updated_at 24 | BEFORE UPDATE ON users 25 | FOR EACH ROW EXECUTE PROCEDURE update_at_column();`), 26 | 27 | knex.schema.raw(`CREATE TRIGGER update_repo_updated_at 28 | BEFORE UPDATE ON repos 29 | FOR EACH ROW EXECUTE PROCEDURE update_at_column();`), 30 | 31 | knex.schema.raw(`CREATE TRIGGER update_tag_updated_at 32 | BEFORE UPDATE ON tags 33 | FOR EACH ROW EXECUTE PROCEDURE update_at_column();`), 34 | 35 | knex.schema.raw(`CREATE TRIGGER update_repo_tag_updated_at 36 | BEFORE UPDATE ON repo_tags 37 | FOR EACH ROW EXECUTE PROCEDURE update_at_column();`), 38 | ]); 39 | }; 40 | 41 | exports.down = (knex, Promise) => { 42 | return Promise.all([ 43 | knex.schema.raw('DROP TRIGGER update_user_updated_at ON users'), 44 | knex.schema.raw('DROP TRIGGER update_repo_updated_at ON repos'), 45 | knex.schema.raw('DROP TRIGGER update_tag_updated_at ON tags'), 46 | knex.schema.raw('DROP TRIGGER update_repo_tag_updated_at ON repo_tags'), 47 | 48 | knex.schema.raw('DROP FUNCTION update_at_column()'), 49 | 50 | knex.schema.raw('ALTER TABLE users ALTER created_at DROP DEFAULT'), 51 | knex.schema.raw('ALTER TABLE repos ALTER created_at DROP DEFAULT'), 52 | knex.schema.raw('ALTER TABLE tags ALTER created_at DROP DEFAULT'), 53 | knex.schema.raw('ALTER TABLE repo_tags ALTER created_at DROP DEFAULT'), 54 | 55 | knex.schema.raw('ALTER TABLE users ALTER updated_at DROP DEFAULT'), 56 | knex.schema.raw('ALTER TABLE repos ALTER updated_at DROP DEFAULT'), 57 | knex.schema.raw('ALTER TABLE tags ALTER updated_at DROP DEFAULT'), 58 | knex.schema.raw('ALTER TABLE repo_tags ALTER updated_at DROP DEFAULT'), 59 | ]); 60 | }; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starboard", 3 | "version": "1.0.0", 4 | "description": "A github starred repo management app.", 5 | "keywords": [], 6 | "homepage": "https://github.com/TryStarboard/Starboard#readme", 7 | "bugs": { 8 | "url": "https://github.com/TryStarboard/Starboard/issues" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/TryStarboard/Starboard.git" 13 | }, 14 | "author": "Daiwei Lu (http://daiwei.lu/)", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "convict": "^1.3.0", 18 | "knex": "^0.11.0", 19 | "pg": "^4.5.5" 20 | }, 21 | "scripts": { 22 | "db:current": "knex migrate:currentVersion", 23 | "db:migrate": "knex migrate:latest" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /scripts/bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git submodule update --init --recursive 4 | -------------------------------------------------------------------------------- /scripts/cibuild: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryStarboard/Starboard/7c3dbcd999bebe15eac888f1873cfa1accdeb905/scripts/cibuild -------------------------------------------------------------------------------- /scripts/console: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryStarboard/Starboard/7c3dbcd999bebe15eac888f1873cfa1accdeb905/scripts/console -------------------------------------------------------------------------------- /scripts/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # ------------------------------ 4 | # Start dev servers 5 | # ------------------------------ 6 | 7 | (cd starboard-ui && npm run watch:build) & 8 | (cd http-server && npm run dev) & 9 | (cd http-server && ./scripts/start-browser-sync) & 10 | (cd job-server && npm run dev) & 11 | (cd websocket-server && npm run dev) & 12 | 13 | # Wait for background tasks to end 14 | wait 15 | -------------------------------------------------------------------------------- /scripts/prod-test-timing: -------------------------------------------------------------------------------- 1 | \n 2 | time_namelookup: %{time_namelookup}\n 3 | time_connect: %{time_connect}\n 4 | time_appconnect: %{time_appconnect}\n 5 | time_pretransfer: %{time_pretransfer}\n 6 | time_redirect: %{time_redirect}\n 7 | time_starttransfer: %{time_starttransfer}\n 8 | ----------\n 9 | time_total: %{time_total}\n 10 | \n 11 | -------------------------------------------------------------------------------- /scripts/server: -------------------------------------------------------------------------------- 1 | #/usr/bin/env bash 2 | 3 | # ------------------------------ 4 | # Start dev servers 5 | # ------------------------------ 6 | 7 | wait 8 | -------------------------------------------------------------------------------- /scripts/setup: -------------------------------------------------------------------------------- 1 | #/usr/bin/env bash 2 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryStarboard/Starboard/7c3dbcd999bebe15eac888f1873cfa1accdeb905/scripts/test -------------------------------------------------------------------------------- /scripts/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git pull --recurse-submodules 4 | git submodule update --recursive 5 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "links": { 3 | "@starboard/starboard-ui": "starboard-ui", 4 | "@starboard/github": "github", 5 | "@starboard/models": "models", 6 | "@starboard/log": "log", 7 | "@starboard/redis": "redis" 8 | } 9 | } 10 | --------------------------------------------------------------------------------