├── .firebaserc
├── .gitignore
├── .jshintignore
├── .jshintrc
├── .npmrc
├── .project
├── .travis.yml
├── LICENSE.md
├── Makefile
├── Procfile
├── README.md
├── checklist.md
├── database.json
├── documentjs.json
├── firebase.json
├── index.js
├── install.js
├── migrations
├── 20150801045523-players.js
├── 20150804053921-add-stats.js
├── 20150809023413-add-stats-time.js
├── 20150816063154-add-users.js
├── 20160301185116-required-fields.js
├── 20160307152540-validate-emails.js
└── 20160313205522-team-ints.js
├── models
├── bookshelf.js
├── game.js
├── player.js
├── stat.js
├── team.js
├── tournament.js
└── user.js
├── package.json
├── public
├── .npmrc
├── app.js
├── app.less
├── build.js
├── components
│ ├── 404.component
│ ├── game
│ │ └── details
│ │ │ ├── details.html
│ │ │ ├── details.js
│ │ │ ├── details.less
│ │ │ ├── details.md
│ │ │ ├── details.stache
│ │ │ ├── details_test.js
│ │ │ └── test.html
│ ├── navigation
│ │ ├── img
│ │ │ └── bitballs-logo-01.svg
│ │ ├── navigation.html
│ │ ├── navigation.js
│ │ ├── navigation.less
│ │ ├── navigation.stache
│ │ ├── navigation_test.js
│ │ └── test.html
│ ├── player
│ │ ├── details
│ │ │ ├── details-test.js
│ │ │ ├── details.html
│ │ │ ├── details.js
│ │ │ ├── details.less
│ │ │ ├── details.md
│ │ │ ├── details.stache
│ │ │ └── test.html
│ │ ├── edit
│ │ │ ├── edit.html
│ │ │ ├── edit.js
│ │ │ ├── edit.md
│ │ │ ├── edit.stache
│ │ │ ├── edit_test.js
│ │ │ └── test.html
│ │ └── list
│ │ │ ├── list.html
│ │ │ ├── list.js
│ │ │ ├── list.stache
│ │ │ ├── list_test.js
│ │ │ └── test.html
│ ├── test.js
│ ├── tournament
│ │ ├── details
│ │ │ ├── details.html
│ │ │ ├── details.js
│ │ │ ├── details.stache
│ │ │ ├── details_test.js
│ │ │ └── test.html
│ │ └── list
│ │ │ ├── list.html
│ │ │ ├── list.js
│ │ │ ├── list.stache
│ │ │ ├── list_test.js
│ │ │ └── test.html
│ └── user
│ │ ├── details
│ │ ├── details.html
│ │ ├── details.js
│ │ ├── details.stache
│ │ ├── details_test.js
│ │ └── test.html
│ │ ├── list
│ │ ├── list.html
│ │ ├── list.js
│ │ ├── list.less
│ │ ├── list.md
│ │ ├── list.stache
│ │ ├── list_test.js
│ │ └── test.html
│ │ └── test.js
├── dev.html
├── img
│ └── bitballs-logo-02.svg
├── index.stache
├── inserted-removed.js
├── is-dev.js
├── models
│ ├── bookshelf-service.js
│ ├── fixtures
│ │ ├── fixtures.js
│ │ ├── games.js
│ │ ├── players.js
│ │ ├── stats.js
│ │ ├── teams.js
│ │ ├── tournaments.js
│ │ └── users.js
│ ├── game.js
│ ├── game_test.js
│ ├── player.js
│ ├── session.js
│ ├── stat.js
│ ├── stat_test.js
│ ├── team.js
│ ├── test.html
│ ├── test.js
│ ├── tournament.js
│ ├── tournament_test.js
│ ├── user.js
│ └── youtube.js
├── package.json
├── prod.html
├── service.js
├── test.html
├── test.js
├── test
│ └── utils.js
└── util
│ ├── prefilter.js
│ ├── test.html
│ └── test.js
├── services
├── adminOnly.js
├── app.js
├── email.js
├── games.js
├── players.js
├── separate-query.js
├── session.js
├── stats.js
├── teams.js
├── tournaments.js
└── users.js
├── test-script.md
└── theme
├── static
├── content_list.js
└── static.js
└── templates
└── helpers.js
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "bitballs-e69ca"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 |
8 | package-lock.json
9 | node_modules
10 | .env
11 | public/node_modules
12 | public/dist
13 | public/package-lock.json
14 | docs/
15 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | node_modules/**
2 | public/node_modules/**
3 | public/dist/**
4 | docs/**
5 | theme/static/
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "it": true,
4 | "describe": true,
5 | "before": true,
6 | "beforeEach": true,
7 | "after": true,
8 | "afterEach": true,
9 | "exports": true,
10 | "bundle": true,
11 | "doneSsr": true,
12 | "Promise": true,
13 | "INLINE_CACHE": true,
14 | "canWait": true,
15 | "confirm": true,
16 | "steal": true,
17 | "YT": true
18 | },
19 | "curly": true,
20 | "eqeqeq": true,
21 | "freeze": true,
22 | "indent": 2,
23 | "latedef": false,
24 | "noarg": true,
25 | "undef": true,
26 | "unused": "vars",
27 | "trailing": true,
28 | "maxdepth": 4,
29 | "boss" : true,
30 | "eqnull": true,
31 | "evil": true,
32 | "loopfunc": true,
33 | "smarttabs": true,
34 | "maxerr" : 200,
35 | "browser": true,
36 | "phantom": true,
37 | "node": true,
38 | "esversion": 6
39 | }
40 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | bitballs
4 |
5 |
6 |
7 |
8 |
9 | com.aptana.ide.core.unifiedBuilder
10 |
11 |
12 |
13 |
14 |
15 | com.aptana.ruby.core.rubynature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: '10'
3 | sudo: required
4 | dist: trusty
5 | addons:
6 | firefox: 52.3.0
7 | postgresql: '9.4'
8 | apt:
9 | packages:
10 | - dbus-x11
11 | install:
12 | - npm install
13 | before_install:
14 | - export DISPLAY=:99.0
15 | - sh -e /etc/init.d/xvfb start
16 | - psql -c 'create database bitballs;' -U postgres
17 | before_deploy:
18 | - git config --global user.email "justin@bitovi.com"
19 | - git config --global user.name "bitballs deploy bot"
20 | - node public/build
21 | - git add public/dist/ --force
22 | - git commit -m "Updating build."
23 | - npm run deploy:ci
24 | deploy:
25 | skip_cleanup: true
26 | provider: heroku
27 | app: bitballs
28 | api_key:
29 | secure: XMm4TV8T+r2XFWdxJfQdPELKl0z08oyQLzkLgb13G3PWMqrtyF4u2Yx/c3Xxv2aT7HGOmsO1HZ4OLGvywmhIRgKbSIJpks/UU0DhH+gwtw1Y7LYCmbo2UCVTvle45Vg6M0LUWXS8ncWjHUr5ZyjbqHqpVRVqvebWBvD6f5NteZf2arKvJVvKzifSReAGfc/L22V3aCLYW57jKeo9RFG2SYGgCxn+Lqf2CEyxm0RARwOKNShqkzRvugvAJVCz2vkEEE8pvLBkc5KpjSqF60NbQGrXZvFELkk8OTx/7pc6DI3N9j4tcifkfzl8+AxXPpwyA/i9Jx6233STI44Y4JkwARYx/r7ylMj/s0f7tcO7RZVtga9zzMM5KmpX97nbXOYd6rS3qnKthoS/N/XXRFpTUxe4QYFtrjoQPo5HdUzaLarI0ehHFFJLhlUr55KPlufKcIjbD5a2aSkVIeb0sfN6uiQtKuz8c4kbZS+uGYd0F7B4pdxnPV1bh2FiuCUE6o44Ny2FVUb/BZSrtAVcO+jqhCaCb1JUTLPEhxV1UHIVsD1gSJAng23GphtHjdlh2uV2Vcy58Gx0BGkPHe25ypT5HsGnFQvECaT0fBd13thqr0MaBYxinRCUhF79crldd3D7LVT6RRENUFSQ2hpO0YqW028vUT+jm3DIugP5vEbnhkk=
30 | env:
31 | global:
32 | secure: GtZ4Jqwgp5V9n+IC51FZT5KjIHMwmBXXntAyXCEQZopX0pGaya6IsJLvbQhkjFhBzJFnLVVkLbTpPaax8Gpf2a7Pwa/4ev8X7n1KxgTeupdpX7Ur8IO6np45n0o2BmE3l7+YOEzt6hC+rxYWPdcsVABTf8No5pPOVFgBL3GfM/fc2evn1wqJ5lGyPvQPJvJiRokznKxxnpqQ2W6C8P+LQy5v3iJJTqnNKWzJOj7dOPznOSdHA3sSJM+u/OjpyrPITsuXGTLXycMr/LK5jpyKUBIrFRZq7aY40+8hX8ZY+dMBHEuh7nIPHd45jqZx3Xk9Vgl974bWk1YK7ZqKk0HaveReqtprcKmEbc6toG22TFSyp3lcXkhLQV+wRWw0yrJ/czGbkuZU4jcO6r4ge75Bfi9+tUhqvMxk1sETkOunYoFpbyiUa7YU/ucH+hCcofphRH6NEtKdus749VdvcPu45rnL/zPxSs+5l8rpwWTp6XpcN0w8+MDYDhMSs6YPs9ltr0fP4U9amRjORU3PGkq7McAaXnenv3r2K5HSG8rFWTV5Te4ckj6MOIiU+tDSx0imClVccMr73NMJV0BVbkOWhef0uDlMb3uFFsPBj/t4OH1UkuhT+UeCNFCHxflj9aJEJe56NLD0ojZFeA1del7Cdl8yqWHL3b2sAkeRyWG09yM=
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Bitovi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | publish-docs:
2 | git checkout -b gh-pages
3 | donejs document
4 | git add -f docs/
5 | git add -f public/components/
6 | git add -f public/models/
7 | git add -f public/node_modules/jquery
8 | git add -f public/node_modules/can
9 | git add -f public/node_modules/can-connect
10 | git add -f public/node_modules/can-fixture
11 | git add -f public/node_modules/can-set
12 | git add -f public/node_modules/can-zone
13 | git add -f public/node_modules/bootstrap
14 | git add -f public/node_modules/done-autorender
15 | git add -f public/node_modules/done-component
16 | git add -f public/node_modules/done-css
17 | git add -f public/node_modules/done-ssr-middleware
18 | git add -f public/node_modules/donejs-cli
19 | git add -f public/node_modules/funcunit
20 | git add -f public/node_modules/generator-donejs
21 | git add -f public/node_modules/moment
22 | git add -f public/node_modules/qunitjs
23 | git add -f public/node_modules/steal
24 | git add -f public/node_modules/steal-qunit
25 | git add -f public/node_modules/steal-systemjs
26 | git add -f public/node_modules/steal-es6-module-loader
27 | git add -f public/node_modules/steal-platform
28 | git add -f public/node_modules/when
29 | git add -f public/node_modules/yeoman-environment
30 | git add -f public/test.html
31 | git commit -m "Publish docs"
32 | git push -f origin gh-pages
33 | git rm -q -r --cached public/node_modules
34 | git checkout -
35 | git branch -D gh-pages
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node index.js
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | @page bitballs Bitballs
2 | @group bitballs.components Components
3 | @group bitballs.clientModels Client Models
4 | @group bitballs.services Services
5 | @group bitballs.serviceModels Service Models
6 | @hide contents
7 |
8 | [](https://travis-ci.org/donejs/bitballs)
9 |
10 | Bitballs is a [DoneJS](https://donejs.com) app that enables users to coordinate
11 | the players, teams, games, rounds and recordings of a basketball tournament.
12 | It also serves as an example of how to use DoneJS with sessions, user
13 | privileges, RESTful services, and ORM models.
14 |
15 | To run the Bitballs app locally, run its tests, or generate its documentation
16 | follow the steps outlined below.
17 |
18 |
19 |
20 |
21 |
22 | - [Setup Environment](#setup-environment)
23 | - [Installing PostgreSQL on OSX](#installing-postgresql-on-osx)
24 | - [Installing PostgreSQL on Linux](#installing-postgresql-on-linux)
25 | - [Installing PostgreSQL on Windows](#installing-postgresql-on-windows)
26 | - [Download Source](#download-source)
27 | - [Install Dependencies](#install-dependencies)
28 | - [Prepare the Database](#prepare-the-database)
29 | - [Start the Server](#start-the-server)
30 | - [Register a User](#register-a-user)
31 | - [Enjoy](#enjoy)
32 |
33 |
34 |
35 | ### Setup Environment
36 |
37 | Make sure you have installed:
38 |
39 | - [Node 5](https://nodejs.org/en/download/)
40 | - NPM 3 *(packaged with Node)*
41 | - [PostgreSQL](https://www.postgresql.org/download/)
42 |
43 | #### Installing PostgreSQL on OSX
44 |
45 | On a Mac, the easiest way to install and configure [PostgreSQL](https://www.postgresql.org)
46 | is using the [brew](https://brew.sh/) utility:
47 |
48 | ```
49 | brew install postgresql
50 | ```
51 |
52 | Pay special attention to the end of the [brew](https://brew.sh/) command's
53 | output, which includes instructions on how to start `postgres`:
54 |
55 | ```
56 | To load postgresql:
57 | launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
58 | Or, if you don't want/need launchctl, you can just run:
59 | postgres -D /usr/local/var/postgres
60 | ```
61 |
62 | The provided `launchctl` command ensures the `postgres` process is always
63 | running, even after a system restart. The alternative `postgres` command
64 | starts the `postgres` process manually.
65 |
66 | We recommend the `launchctl` option. If desired, `postgres` can be
67 | stopped and uninstalled by running:
68 |
69 | ```
70 | brew uninstall postgresql
71 | ```
72 |
73 | #### Installing PostgreSQL on Linux
74 |
75 | *Coming Soon*
76 |
77 | #### Installing PostgreSQL on Windows
78 |
79 | Download and use the graphical installer available on [postgresql.org](http://www.postgresql.org/download/windows/). Make sure you host it listen to port `5432`.
80 |
81 | Open `pg_hba.conf`, which should be in _C:\Program Files\PostgreSQL\9.5\data_, and change from `md5` authentication to `trust`. For example, change:
82 |
83 | > host all all 127.0.0.1/32 md5
84 |
85 | to:
86 |
87 | > host all all 127.0.0.1/32 trust
88 |
89 | `trust` should not be used in a production environment. We are only using it here as a substitute for the `peer` mode available in UNIX environments. Read more about it [here](http://www.postgresql.org/docs/9.5/static/auth-methods.html).
90 |
91 |
92 |
93 | Finally, using `pgAdmin III` graphical database manager, which should have been installed with `postgres`, create a `bitballs` database.
94 |
95 |
96 | ### Download Source
97 |
98 | Clone this repo using git:
99 |
100 | ```
101 | git clone https://github.com/donejs/bitballs.git
102 | ```
103 |
104 | Navigate to the repository's directory
105 |
106 | ```
107 | cd bitballs
108 | ```
109 |
110 | ### Prepare the Database
111 |
112 | Make sure the `postgres` process is running:
113 |
114 | ```
115 | ps | grep postgres
116 | ```
117 |
118 | You should see "postgres -D" among the output:
119 |
120 | ```
121 | 92831 ttys000 0:00.02 postgres -D /usr/local/var/postgres
122 | 92856 ttys000 0:00.00 grep postgres
123 | ```
124 |
125 | With that confirmed we can create the database that the bitballs app
126 | will persist its data to:
127 |
128 | ```
129 | createdb bitballs
130 | ```
131 |
132 | ### Install Dependencies
133 |
134 | To install the project's JavaScript dependencies run:
135 |
136 | ```
137 | npm install
138 | ```
139 |
140 | Additionally DoneJS's command line utilities need to be installed globally:
141 |
142 | ```
143 | npm install -g donejs-cli
144 | ```
145 |
146 | ### Start the Server
147 |
148 | With all the prerequisite setup completed the server can be started by running:
149 |
150 | ```
151 | donejs develop
152 | ```
153 |
154 | ### Register a User
155 |
156 | Navigate to [http://localhost:5000/register](http://localhost:5000/register)
157 | in your browser and follow the instructions.
158 |
159 | ### Enjoy
160 |
161 | You're finished! Explore some of the app's features:
162 |
163 | - Live reload (`donejs develop`)
164 | - Run the tests (`donejs test`)
165 | - Generate the documentation (`donejs document`)
166 |
--------------------------------------------------------------------------------
/checklist.md:
--------------------------------------------------------------------------------
1 | ## What is the project's vision?
2 |
3 | > This is typically a single sentence that describes what the project aspires to be.
4 | > Example: "A JS framework that allows developers to build better apps, faster".
5 | > If this doesn't exist, write "none".
6 |
7 | A guide and application that:
8 |
9 | - Teaches people how to solve common problems with DoneJS
10 | - Demonstrates that DoneJS elegantly solves common problems.
11 |
12 | Produce people who can build a good guide without much
13 | oversite.
14 |
15 | ## How will the project measure success?
16 |
17 | > Example: Increase mobile conversion rates to 0.75-1.0%, currently ~0.3%. If this doesn't exist, write "none".
18 |
19 | - Approximately as many page views as PMO.
20 | - If a question is directly related to parts of this guide,
21 | we should be able to post a link to the relavant section and there are no follow ups except for
22 | ones not covered by the guide. Basically, if someone asks, how to deal with cookie based sessions,
23 | we should post to this guide's `session` section.
24 | - Github Stars?
25 | - Roomplanner and Jobcostracker are successful without Justin managing them (but he's involved when appropriate).
26 |
27 |
28 | ## What is the strategy for accomplishing the project's goals?
29 |
30 | > What is the strategy for accomplishing the project's goals?
31 |
32 | - Establish clear expectations.
33 | - Goal oriented
34 | - Start and end of day status updates.
35 | - Getting help is the solution to delivery quality wtihout working longer hours.
36 | - Manage by over-async-communicating tasks until developers can take over.
37 | - Be proactive if you don't have an issue. Upward manage expectations.
38 |
39 | - Sharing knowledge
40 | - Daily code reviews of previous day's pull requests or progress.
41 | - Commit to your branch every night.
42 | - Pairing?
43 |
44 | ## What is the project's roadmap? What are the goals, plans and release schedule after the current release?
45 |
46 | - [ ] - Identify common problems in Bitballs that are not in
47 | PMO and DoneJS Chat.
48 |
49 | - Sessions, simple admin rights.
50 | - Relationships between models + derived values.
51 | - Node restful services and server-side rendering mixed.
52 | - Ignore parts of SSR
53 |
54 | - [ ] - Outline the guide.
55 |
56 | - [ ] - Get DoneJS essential features in place.
57 |
58 | - [ ] - Justin write the guide.
59 |
60 | - [ ] - Convert to a "good" DoneJS app
61 | - tests
62 | - docs
63 | - demo pages
64 |
65 | - Put it together.
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/database.json:
--------------------------------------------------------------------------------
1 | {
2 | "dev": "postgres://localhost:5432/bitballs",
3 | "prod": {
4 | "ENV": "DATABASE_URL",
5 | "driver": "pg"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/documentjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "sites" : {
3 | "docs": {
4 | "glob" : {
5 | "ignore": ["{node_modules,public/node_modules,migrations,public/dist}/**/*"]
6 | },
7 | "parent": "bitballs",
8 | "dest": "docs"
9 | }
10 | },
11 | "siteDefaults": {
12 | "static": "theme/static",
13 | "source": "https://github.com/donejs/bitballs",
14 | "templates" : "theme/templates"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "firebase": "bitballs-e69ca",
4 | "public": "./public/dist",
5 | "headers": [
6 | {
7 | "source": "/**",
8 | "headers": [
9 | {
10 | "key": "Access-Control-Allow-Origin",
11 | "value": "*"
12 | }
13 | ]
14 | }
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = require('./services/app');
3 | var exec = require( "child_process" ).exec;
4 | var cookieParser = require('cookie-parser');
5 | var path = require("path");
6 |
7 | app.set('port', (process.env.PORT || 5000));
8 |
9 | app.use( express.static(__dirname + '/public') );
10 |
11 | app.use(cookieParser());
12 |
13 | if ( process.argv.indexOf( "--slow" ) !== -1 ) {
14 | console.log("Delaying everything 1 second");
15 | app.use( function ( req, res, next ) {
16 | setTimeout(next, 1000);
17 | });
18 | }
19 |
20 | require('./services/session');
21 |
22 | require('./services/games');
23 | require('./services/players');
24 | require('./services/stats');
25 | require('./services/teams');
26 | require('./services/tournaments');
27 | require('./services/users');
28 |
29 | //can-ssr:
30 | app.use( "/", require('./public/service') );
31 |
32 | app.listen(app.get('port'), function() {
33 | console.log('Node app is running on port', app.get('port'));
34 | });
35 |
36 | if ( process.argv.indexOf( "--develop" ) !== -1 ) {
37 | //is dev mode so do live reload
38 | var child = exec( path.join("node_modules",".bin","steal-tools live-reload"), {
39 | cwd: process.cwd() + "/public"
40 | });
41 |
42 | child.stdout.pipe( process.stdout );
43 | child.stderr.pipe( process.stderr );
44 | }
45 |
--------------------------------------------------------------------------------
/install.js:
--------------------------------------------------------------------------------
1 | var exec = require( "child_process" ).exec;
2 |
3 | var child = exec( "npm install", {
4 | cwd: process.cwd() + "/public"
5 | });
6 |
7 | child.stdout.pipe( process.stdout );
8 | child.stderr.pipe( process.stderr );
9 |
--------------------------------------------------------------------------------
/migrations/20150801045523-players.js:
--------------------------------------------------------------------------------
1 | exports.up = function(db, callback) {
2 | db.createTable('players', {
3 | id: { type: 'int', primaryKey: true, autoIncrement: true },
4 | name: 'string',
5 | weight: 'int',
6 | height: 'int',
7 | birthday: 'date',
8 | profile: 'text',
9 | startRank: 'string'
10 | }, callback);
11 | };
12 |
13 | exports.down = function(db, callback) {
14 | db.dropTable('players', callback);
15 | };
16 |
--------------------------------------------------------------------------------
/migrations/20150804053921-add-stats.js:
--------------------------------------------------------------------------------
1 | var async = require('async');
2 |
3 | exports.up = function(db, callback) {
4 | async.series([
5 | db.createTable.bind(db, 'tournaments', {
6 | id: { type: 'int', primaryKey: true, autoIncrement: true },
7 | date: 'date'
8 | }),
9 | db.createTable.bind(db, 'teams', {
10 | id: { type: 'int', primaryKey: true, autoIncrement: true },
11 | tournamentId: 'int',
12 | name: 'string',
13 | color: 'string',
14 | player1Id: 'int',
15 | player2Id: 'int',
16 | player3Id: 'int',
17 | player4Id: 'int'
18 | }),
19 | db.createTable.bind(db, 'games', {
20 | id: { type: 'int', primaryKey: true, autoIncrement: true },
21 | tournamentId: 'int',
22 | round: 'string',
23 | court: 'string',
24 | videoUrl: 'string',
25 | homeTeamId: 'string',
26 | awayTeamId: 'string'
27 | }),
28 | db.createTable.bind(db, 'stats', {
29 | id: { type: 'int', primaryKey: true, autoIncrement: true },
30 | gameId: 'int',
31 | playerId: 'int',
32 | type: 'string'
33 | })
34 | ], callback);
35 | };
36 |
37 | exports.down = function(db, callback) {
38 | async.series([
39 | db.dropTable.bind(db, 'tournaments'),
40 | db.dropTable.bind(db, 'teams'),
41 | db.dropTable.bind(db, 'games'),
42 | db.dropTable.bind(db, 'stats')
43 | ], callback);
44 | };
45 |
46 |
--------------------------------------------------------------------------------
/migrations/20150809023413-add-stats-time.js:
--------------------------------------------------------------------------------
1 | var async = require('async');
2 |
3 | exports.up = function(db, callback) {
4 | async.series([
5 | db.addColumn.bind(db, "stats","time",{type: 'int'}),
6 | db.addColumn.bind(db, "stats","value",{type: 'int'})
7 | ], callback);
8 | };
9 |
10 | exports.down = function(db, callback) {
11 | async.series([
12 | db.removeColumn.bind(db, "stats","time"),
13 | db.removeColumn.bind(db, "stats","value")
14 | ], callback);
15 | };
16 |
--------------------------------------------------------------------------------
/migrations/20150816063154-add-users.js:
--------------------------------------------------------------------------------
1 | exports.up = function(db, callback) {
2 | db.createTable('users', {
3 | id: { type: 'int', primaryKey: true, autoIncrement: true },
4 | name: 'string',
5 | password: 'string',
6 | email: {type: 'string', unique: true},
7 | isAdmin: 'boolean'
8 | }, callback);
9 | };
10 |
11 | exports.down = function(db, callback) {
12 | db.dropTable('users', callback);
13 | };
14 |
--------------------------------------------------------------------------------
/migrations/20160301185116-required-fields.js:
--------------------------------------------------------------------------------
1 | var async = require('async');
2 |
3 | exports.up = function(db, callback) {
4 | async.series([
5 | db.changeColumn.bind(db, 'players', 'name', {
6 | notNull: true
7 | }),
8 | db.changeColumn.bind(db, 'users', 'password', {
9 | notNull: true
10 | }),
11 | db.changeColumn.bind(db, 'tournaments', 'date', {
12 | notNull: true
13 | })
14 | ],
15 | callback);
16 | };
17 |
18 | exports.down = function(db, callback) {
19 | async.series([
20 | db.changeColumn.bind(db, 'players', 'name', {
21 | notNull: false
22 | }),
23 | db.changeColumn.bind(db, 'users', 'password', {
24 | notNull: false
25 | }),
26 | db.changeColumn.bind(db, 'tournaments', 'date', {
27 | notNull: false
28 | })
29 | ],
30 | callback);
31 | };
32 |
--------------------------------------------------------------------------------
/migrations/20160307152540-validate-emails.js:
--------------------------------------------------------------------------------
1 | var async = require('async');
2 |
3 | exports.up = function(db, callback) {
4 | async.series([
5 | db.addColumn.bind(db, 'users', 'verified', {
6 | type: "boolean",
7 | notNull: true,
8 | defaultValue: false
9 | }),
10 | db.addColumn.bind(db, 'users', 'verificationHash', {
11 | type: "string",
12 | length: 100
13 | })
14 | ],
15 | callback);
16 | };
17 |
18 | exports.down = function(db, callback) {
19 | async.series([
20 | db.removeColumn.bind(db, 'users', 'verified' ),
21 | db.removeColumn.bind(db, 'users', 'verificationHash' )
22 | ],
23 | callback);
24 | };
25 |
--------------------------------------------------------------------------------
/migrations/20160313205522-team-ints.js:
--------------------------------------------------------------------------------
1 | var async = require('async');
2 |
3 | exports.up = function(db, callback) {
4 | async.series([
5 | db.runSql.bind(db, 'ALTER TABLE games ALTER COLUMN "homeTeamId" TYPE integer USING "homeTeamId"::numeric'),
6 | db.runSql.bind(db, 'ALTER TABLE games ALTER COLUMN "awayTeamId" TYPE integer USING "awayTeamId"::numeric')
7 | ],
8 | callback);
9 | };
10 |
11 | exports.down = function(db, callback) {
12 | async.series([
13 | db.changeColumn.bind(db, 'games', 'homeTeamId', {
14 | type: 'string'
15 | }),
16 | db.changeColumn.bind(db, 'games', 'awayTeamId', {
17 | type: 'string'
18 | })
19 | ],
20 | callback);
21 | };
22 |
--------------------------------------------------------------------------------
/models/bookshelf.js:
--------------------------------------------------------------------------------
1 | var dbConfig = require('../database.json');
2 | var environmentKey = process.env.NODE_ENV === 'production' ? 'prod' : 'dev';
3 | var dbEnvironmentConfig = dbConfig[environmentKey];
4 |
5 | // Use the string itself or use the provided environment variable
6 | var connectionString = typeof dbEnvironmentConfig === 'string' ?
7 | dbEnvironmentConfig :
8 | process.env[dbEnvironmentConfig.ENV];
9 |
10 | var knex = require('knex')({
11 | client: 'pg',
12 | connection: connectionString
13 | });
14 |
15 | module.exports = require('bookshelf')(knex);
16 |
--------------------------------------------------------------------------------
/models/game.js:
--------------------------------------------------------------------------------
1 | var bookshelf = require("./bookshelf");
2 |
3 | /**
4 | * @module {bookshelf.Model} models/game Game
5 | * @parent bitballs.serviceModels
6 | *
7 | * @group models/game.properties 0 properties
8 | *
9 | * @signature `new Game(properties)`
10 | * Creates an instance of a model.
11 | *
12 | * @param {Object} properties Initial values for this model's properties.
13 | */
14 |
15 | var Game = bookshelf.Model.extend(
16 | /** @prototype **/
17 | {
18 | /**
19 | * @property {String<"games">} models/game.properties.tableName tableName
20 | * @parent models/game.properties
21 | *
22 | * Indicates which database table Bookshelf.js will query against.
23 | **/
24 | tableName: 'games',
25 | /**
26 | * @function
27 | *
28 | * Informs Bookshelf.js that the `stats` property will be a list of
29 | * [models/stat] models with a `gameId` that
30 | * matches the `id` specified in the query.
31 | **/
32 | stats: function(){
33 | var Stat = require("./stat");
34 | return this.hasMany(Stat,"gameId");
35 | },
36 | /**
37 | * @function
38 | *
39 | * Informs Bookshelf.js that the `homeTeam` property will be a [models/team]
40 | * model with an `id` that matches the `homeTeamId` specified in the query.
41 | **/
42 | homeTeam: function(){
43 | var Team = require("./team");
44 | return this.belongsTo(Team,"homeTeamId");
45 | },
46 | /**
47 | * @function
48 | *
49 | * Informs Bookshelf.js that the `awayTeam` property will be a [models/team]
50 | * model with an `id` that matches the `awayTeamId` specified in the query.
51 | **/
52 | awayTeam: function(){
53 | var Team = require("./team");
54 | return this.belongsTo(Team,"awayTeamId");
55 | },
56 | /**
57 | * @function
58 | *
59 | * Informs Bookshelf.js that the `homeTeam` property will be a [models/tournament]
60 | * model with an `id` that matches the `tournamentId` specified in the query.
61 | **/
62 | tournament: function(){
63 | var Tournament = require("./tournament");
64 | return this.belongsTo(Tournament, "tournamentId");
65 | }
66 | });
67 |
68 | module.exports = Game;
69 |
--------------------------------------------------------------------------------
/models/player.js:
--------------------------------------------------------------------------------
1 | var bookshelf = require("./bookshelf");
2 | var checkit = require("checkit");
3 |
4 | /**
5 | * @module {bookshelf.Model} models/player Player
6 | * @parent bitballs.serviceModels
7 | *
8 | * @group models/player.properties 0 properties
9 | *
10 | * @signature `new Player(properties)`
11 | * Creates an instance of a model.
12 | *
13 | * @param {Object} properties Initial values for this model's properties.
14 | */
15 |
16 | var Player = bookshelf.Model.extend(
17 | /** @prototype **/
18 | {
19 | /**
20 | * @property {String<"players">} models/player.properties.tableName tableName
21 | * @parent models/player.properties
22 | *
23 | * Indicates which database table Bookshelf.js will query against.
24 | **/
25 | tableName: 'players',
26 | /**
27 | * @function
28 | *
29 | * Binds to the "saving" event and specifies [models/player.prototype.validateSave validateSave]
30 | * as the handler during initialization.
31 | **/
32 | initialize: function(){
33 | this.on('saving', this.validateSave);
34 | },
35 | /**
36 | * @function
37 | *
38 | * Validates that `name` is defined on `this.attributes`.
39 | *
40 | * @return {Promise}
41 | **/
42 | validateSave: function(){
43 | return checkit({
44 | name: 'required'
45 | }).run(this.attributes);
46 | },
47 | /**
48 | * @function
49 | *
50 | * Informs Bookshelf.js that the `stats` property will be a list of
51 | * [models/stat] models with a `playerId` that
52 | * matches the `id` specified in the query.
53 | **/
54 | stats: function(){
55 | var Stat = require("./stat");
56 | return this.hasMany(Stat,"playerId");
57 | },
58 | games: function(){
59 | var Game = require("./game");
60 | return this.hasMany(Game,"playerId");
61 | }
62 | });
63 |
64 | module.exports = Player;
65 |
--------------------------------------------------------------------------------
/models/stat.js:
--------------------------------------------------------------------------------
1 | var bookshelf = require("./bookshelf");
2 |
3 | /**
4 | * @module {bookshelf.Model} models/stat Stat
5 | * @parent bitballs.serviceModels
6 | *
7 | * @group models/stat.properties 0 properties
8 | *
9 | * @signature `new Stat(properties)`
10 | * Creates an instance of a model.
11 | *
12 | * @param {Object} properties Initial values for this model's properties.
13 | */
14 |
15 | var Stat = bookshelf.Model.extend(
16 | /** @prototype **/
17 | {
18 | /**
19 | * @property {String<"stats">} models/stat.properties.tableName tableName
20 | * @parent models/stat.properties
21 | *
22 | * Indicates which database table Bookshelf.js will query against.
23 | **/
24 | tableName: 'stats',
25 | /**
26 | * @function
27 | *
28 | * Informs Bookshelf.js that the `game` property will be a [models/game]
29 | * model with an `id` that matches the `gameId` specified in the query.
30 | **/
31 | game: function(){
32 | var Game = require("./game");
33 | return this.belongsTo(Game,"gameId");
34 | },
35 | /**
36 | * @function
37 | *
38 | * Informs Bookshelf.js that the `player` property will be a [models/player]
39 | * model with an `id` that matches the `playerId` specified in the query.
40 | **/
41 | player: function(){
42 | var Player = require("./player");
43 | return this.belongsTo(Player,"playerId");
44 | }
45 | });
46 |
47 | module.exports = Stat;
48 |
--------------------------------------------------------------------------------
/models/team.js:
--------------------------------------------------------------------------------
1 | var bookshelf = require("./bookshelf");
2 | var checkit = require("checkit");
3 |
4 | /**
5 | * @module {bookshelf.Model} models/team Team
6 | * @parent bitballs.serviceModels
7 | *
8 | * @group models/team.properties 0 properties
9 | *
10 | * @signature `new Team(properties)`
11 | * Creates an instance of a model.
12 | *
13 | * @param {Object} properties Initial values for this model's properties.
14 | */
15 |
16 | var Team = bookshelf.Model.extend(
17 | /** @prototype **/
18 | {
19 | /**
20 | * @property {String<"teams">} models/team.properties.tableName tableName
21 | * @parent models/team.properties
22 | *
23 | * Indicates which database table Bookshelf.js will query against.
24 | **/
25 | tableName: 'teams',
26 | initialize: function(){
27 | this.on('saving', this.validateSave);
28 | },
29 | /**
30 | * @function
31 | *
32 | * Validates fields and produces informative error messages
33 | * if the team can not be saved.
34 | */
35 | validateSave: function(){
36 | return checkit({
37 | player1Id: {rule: 'required', message: 'Player 1 is required'},
38 | player2Id: {rule: 'required', message: 'Player 2 is required'},
39 | player3Id: {rule: 'required', message: 'Player 3 is required'},
40 | player4Id: {rule: 'required', message: 'Player 4 is required'}
41 | }).run(this.attributes);
42 | },
43 | /**
44 | * @function
45 | *
46 | * Informs Bookshelf.js that the `homeGames` property will be a list of
47 | * [models/game] models with a `homeTeamId` that
48 | * matches the `id` specified in the query.
49 | **/
50 | homeGames: function(){
51 | var Game = require("./game");
52 | return this.hasMany(Game,"homeTeamId");
53 | },
54 | /**
55 | * @function
56 | *
57 | * Informs Bookshelf.js that the `awayGames` property will be a list of
58 | * [models/game] models with a `awayTeamId` that
59 | * matches the `id` specified in the query.
60 | **/
61 | awayGames: function(){
62 | var Game = require("./game");
63 | return this.hasMany(Game,"awayTeamId");
64 | },
65 | /**
66 | * @function
67 | *
68 | * Informs Bookshelf.js that the `player1` property will be a [models/player]
69 | * model with an `id` that matches the `player1Id` specified in the query.
70 | **/
71 | player1: function(){
72 | var Player = require("./player");
73 | return this.belongsTo(Player,"player1Id");
74 | },
75 | /**
76 | * @function
77 | *
78 | * Informs Bookshelf.js that the `player2` property will be a [models/player]
79 | * model with an `id` that matches the `player2Id` specified in the query.
80 | **/
81 | player2: function(){
82 | var Player = require("./player");
83 | return this.belongsTo(Player,"player2Id");
84 | },
85 | /**
86 | * @function
87 | *
88 | * Informs Bookshelf.js that the `player3` property will be a [models/player]
89 | * model with an `id` that matches the `player3Id` specified in the query.
90 | **/
91 | player3: function(){
92 | var Player = require("./player");
93 | return this.belongsTo(Player,"player3Id");
94 | },
95 | /**
96 | * @function
97 | *
98 | * Informs Bookshelf.js that the `player4` property will be a [models/player]
99 | * model with an `id` that matches the `player4Id` specified in the query.
100 | **/
101 | player4: function(){
102 | var Player = require("./player");
103 | return this.belongsTo(Player,"player4Id");
104 | }
105 | });
106 |
107 | module.exports = Team;
108 |
--------------------------------------------------------------------------------
/models/tournament.js:
--------------------------------------------------------------------------------
1 | var bookshelf = require("./bookshelf");
2 | var checkit = require("checkit");
3 |
4 | /**
5 | * @module {bookshelf.Model} models/tournament Tournament
6 | * @parent bitballs.serviceModels
7 | *
8 | * @group models/tournament.properties 0 properties
9 | *
10 | * @signature `new Tournament(properties)`
11 | * Creates an instance of a model.
12 | *
13 | * @param {Object} properties Initial values for this model's properties.
14 | */
15 |
16 | var Tournament = bookshelf.Model.extend(
17 | /** @prototype **/
18 | {
19 | /**
20 | * @property {String<"tournaments">} models/tournament.properties.tableName tableName
21 | * @parent models/tournament.properties
22 | *
23 | * Indicates which database table Bookshelf.js will query against.
24 | **/
25 | tableName: 'tournaments',
26 | /**
27 | * @function
28 | *
29 | * Binds to the "saving" event and specifies [models/tournament.prototype.validateSave validateSave]
30 | * as the handler during initialization.
31 | **/
32 | initialize: function(){
33 | this.on('saving', this.validateSave);
34 | },
35 | /**
36 | * @function
37 | *
38 | * Validates that `date` is defined on `this.attributes`.
39 | *
40 | * @return {Promise}
41 | **/
42 | validateSave: function(){
43 | return checkit({
44 | date: ['required']
45 | }).run(this.attributes);
46 | },
47 | /**
48 | * @function
49 | *
50 | * Informs Bookshelf.js that the `games` property will be a list of
51 | * [models/game] models with a `tournamentId` that
52 | * matches the `id` specified in the query.
53 | **/
54 | games: function(){
55 | var Game = require("./game");
56 | return this.hasMany(Game,"tournamentId");
57 | }
58 | });
59 |
60 | module.exports = Tournament;
61 |
--------------------------------------------------------------------------------
/models/user.js:
--------------------------------------------------------------------------------
1 | var bookshelf = require("./bookshelf");
2 | var checkit = require("checkit");
3 |
4 | /**
5 | * @module {bookshelf.Model} models/user User
6 | * @parent bitballs.serviceModels
7 | *
8 | * @group models/user.properties 0 properties
9 | *
10 | * @signature `new User(properties)`
11 | * Creates an instance of a model.
12 | *
13 | * @param {Object} properties Initial values for this model's properties.
14 | */
15 |
16 | var User = bookshelf.Model.extend(
17 | /** @prototype **/
18 | {
19 | /**
20 | * @property {String<"users">} models/user.properties.tableName tableName
21 | * @parent models/user.properties
22 | *
23 | * Indicates which database table Bookshelf.js will query against.
24 | **/
25 | tableName: 'users',
26 | /**
27 | * @function
28 | *
29 | * Binds to the "saving" event and specifies [models/user.prototype.validateSave validateSave]
30 | * as the handler during initialization.
31 | **/
32 | initialize: function(){
33 | this.on('saving', this.validateSave);
34 | },
35 | /**
36 | * @function
37 | *
38 | * Validates that `email` is defined and formatted as an email address
39 | * and `password` is defiend on `this.attributes`.
40 | *
41 | * @return {Promise}
42 | **/
43 | validateSave: function(){
44 | return checkit({
45 | email: ['required', 'email'],
46 | password: 'required'
47 | }).run(this.attributes);
48 | }
49 | });
50 |
51 | module.exports = User;
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitballs",
3 | "version": "0.4.1",
4 | "description": "A basketball tournament app",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "build": "node public/build.js",
9 | "develop": "node index.js --develop",
10 | "db-migrate": "db-migrate up",
11 | "document": "documentjs -d",
12 | "test": "npm run jshint && cd public/ && npm test",
13 | "jshint": "jshint ./ --config .jshintrc",
14 | "install": "node install.js && npm run db-migrate",
15 | "deploy": "firebase deploy",
16 | "deploy:ci": "firebase deploy --token \"$FIREBASE_TOKEN\""
17 | },
18 | "dependencies": {
19 | "async": "^2.4.1",
20 | "bcrypt-nodejs": "0.0.3",
21 | "body-parser": "^1.13.3",
22 | "bookshelf": "^0.10.3",
23 | "checkit": "^0.7.0",
24 | "cookie-parser": "^1.4.1",
25 | "db-migrate": "^0.9.26",
26 | "ejs": "^2.3.1",
27 | "express": "^4.15.3",
28 | "express-session": "^1.11.3",
29 | "knex": "^0.12.0",
30 | "lodash": "^4.17.4",
31 | "nodemailer": "^2.7.2",
32 | "passport": "^0.2.2",
33 | "passport-local": "^1.0.0",
34 | "pg": "4.5.6"
35 | },
36 | "engines": {
37 | "node": "6.11.0"
38 | },
39 | "repository": {
40 | "type": "git",
41 | "url": "https://github.com/donejs/bitballs.git"
42 | },
43 | "keywords": [
44 | "node",
45 | "heroku",
46 | "express"
47 | ],
48 | "license": "MIT",
49 | "devDependencies": {
50 | "documentjs": "^0.4.4",
51 | "donejs": "^3.0.0",
52 | "donejs-cli": "^3.0.0",
53 | "firebase-tools": "^6.0.1",
54 | "jshint": "^2.9.1",
55 | "maildev": "^0.12.2"
56 | },
57 | "urls": {
58 | "prod": "https://bitballs.herokuapp.com/",
59 | "dev": "http://localhost:5000/"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/public/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/public/app.less:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0 0 15px 0;
3 | font-family: "Helvetica Neue-Light","Helvetica Neue Light","Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;
4 | font-weight: 300;
5 | background: url(./img/bitballs-logo-02.svg) no-repeat;
6 | background-size: 100% auto;
7 | }
8 |
--------------------------------------------------------------------------------
/public/build.js:
--------------------------------------------------------------------------------
1 | var path = require("path");
2 | var stealTools = require("steal-tools");
3 |
4 | var config = {
5 | config: path.join(__dirname, "package.json!npm")
6 | };
7 |
8 | module.exports = stealTools
9 | .build(config, {
10 | bundleAssets: true
11 | });
12 |
--------------------------------------------------------------------------------
/public/components/404.component:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 404: This is not the page you are looking for. |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/components/game/details/details.html:
--------------------------------------------------------------------------------
1 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/public/components/game/details/details.less:
--------------------------------------------------------------------------------
1 | game-details .stats-container {
2 | position: relative;
3 | width: 75%;
4 | border-left: 1px solid #ddd;
5 | border-right: 1px solid #ddd;
6 | }
7 | game-details .stat-point {
8 | font-family: Tahoma, Verdana, sans-serif;
9 | position: absolute;
10 | font-size: 8px;
11 | margin: 0 auto;
12 | text-align: center;
13 | display: inline-block;
14 | padding: 3px 2px;
15 | margin-bottom: 0;
16 | font-weight: normal;
17 | line-height: 1.5;
18 | text-align: center;
19 | white-space: nowrap;
20 | vertical-align: middle;
21 | -ms-touch-action: manipulation;
22 | touch-action: manipulation;
23 | cursor: pointer;
24 | -webkit-user-select: none;
25 | -moz-user-select: none;
26 | -ms-user-select: none;
27 | user-select: none;
28 | background-image: none;
29 | border: 1px solid transparent;
30 | border-radius: 2px;
31 | transform: translate( -50%, 0);
32 |
33 | .destroy-btn {
34 | display: none;
35 | padding: 2px;
36 | font-size: 0.9em;
37 | opacity: 0.5;
38 |
39 | &:hover {
40 | opacity: 1.0;
41 | }
42 | }
43 | }
44 | game-details .stat-point:hover {
45 | font-size: 12px;
46 | padding: 1px;
47 |
48 | .destroy-btn {
49 | display: inline-block;
50 | }
51 | }
52 | .youtube-container {
53 | padding-bottom: 15px;
54 | height: auto;
55 | #youtube-player {
56 | width: 100%;
57 | background: black;
58 | }
59 | }
60 | #player-pos {
61 | width: 1px;
62 | border-left: solid 1px #c46d3d;
63 | z-index: 0;
64 | position: absolute;
65 | }
66 | game-details {
67 | .stat-1P {
68 | background-color: #2d8e61;
69 | color: white;
70 | }
71 | .stat-1PA {
72 | background-color: #8cad9d;
73 | color: white;
74 | }
75 | .stat-2P {
76 | background-color: #2d8e61;
77 | color: white;
78 | }
79 | .stat-2PA {
80 | background-color: #8cad9d;
81 | color: white;
82 | }
83 | .stat-ORB {
84 | background-color: #9367a5;
85 | color: white;
86 | }
87 | .stat-DRB {
88 | color: #4E388C;
89 | border: solid 1px #9367a5;
90 | }
91 | .stat-Ast {
92 | background-color: #c46d3d;
93 | color: black;
94 | }
95 |
96 | .stat-Stl {
97 | border: solid 1px #1C2E8C;
98 | color: #1C2E8C;
99 | }
100 | .stat-Blk {
101 | border: solid 1px #4C2D0F;
102 | color: #4C2D0F;
103 | }
104 | .stat-To {
105 | background-color: red;
106 | color: white;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/public/components/game/details/details.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donejs/bitballs/9670ae729d4cbfe0900d20e654d2bf96aa402c6a/public/components/game/details/details.md
--------------------------------------------------------------------------------
/public/components/game/details/details.stache:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{^ if(gamePromise.isRejected) }}
4 |
5 |
10 |
11 |
12 |
HOME: {{ game.homeTeam.color }} - {{ game.homeTeam.name }}
13 |
AWAY: {{ game.awayTeam.color }} - {{ game.awayTeam.name }}
14 |
Final Score {{ finalScore.home }} - {{ finalScore.away }}
15 |
Current Score {{ currentScore.home }} - {{ currentScore.away }}
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{# each(game.teams) }}
23 |
24 | {{ color }} - {{ name }} |
25 |
26 | {{# each(players) }}
27 |
28 | {{ name }} |
29 |
30 | {{# each(scope.vm.statsForPlayerId(id)) }}
31 |
33 | {{ type }}
34 | {{# if(scope.vm.session.isAdmin()) }}
35 |
37 | {{/ if }}
38 |
39 | {{/ each }}
40 | |
41 |
42 | {{/ each }}
43 | {{/ each }}
44 |
45 |
46 |
47 |
48 |
49 | {{# if(stat) }}
50 |
51 |
52 |
53 |
Add Stat for {{ stat.player.name }}
54 |
81 |
82 |
83 | {{/ if }}
84 | {{ else }}
85 | Game not found.
86 | {{/ if }}
87 |
--------------------------------------------------------------------------------
/public/components/game/details/details_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from "steal-qunit";
2 | import Session from "~/models/session";
3 | import { ViewModel as DetailsViewModel } from "./details";
4 | import { games } from "~/models/fixtures/games";
5 | import createGamesFixtures from "~/models/fixtures/games";
6 | import F from 'funcunit';
7 | import { fixture, stache, viewModel as canViewModel } from "can";
8 | import $ from 'jquery';
9 | import User from "~/models/user";
10 |
11 | var deepEqual = QUnit.deepEqual,
12 | ok = QUnit.ok,
13 | notOk = QUnit.notOk;
14 |
15 | F.attach(QUnit);
16 |
17 | QUnit.module("bitballs/game/details/", {
18 | setup: function() {
19 | localStorage.clear();
20 | fixture.delay = 1;
21 | createGamesFixtures();
22 | this.vm = new DetailsViewModel({
23 | gameId: 1,
24 | session: new Session()
25 | });
26 | }
27 | });
28 |
29 | QUnit.test("loads game data", function() {
30 | QUnit.stop();
31 |
32 | this.vm.on("game", function(ev, game) {
33 | deepEqual(game.get(), games, "fetched game data matches fixture");
34 | QUnit.start();
35 | });
36 |
37 | this.vm.gamePromise.catch(function(err) {
38 | ok(false, "game fetch failed");
39 | QUnit.start();
40 | });
41 | });
42 |
43 | QUnit.test("correctly sums score", function() {
44 | QUnit.stop();
45 |
46 | var vm = this.vm;
47 | vm.on("game", function(ev, game) {
48 | deepEqual(vm.finalScore, {
49 | home: 3,
50 | away: 5
51 | });
52 | QUnit.start();
53 | });
54 | });
55 |
56 | QUnit.test("correctly sums the current score", function () {
57 | QUnit.stop();
58 | var vm = this.vm;
59 | console.log("ON GAME");
60 | vm.on('game', function whenGameIsLoaded () {
61 | console.log("GAME ON");
62 | /*
63 | We assume each game starts with zero scores.
64 | So, no pickup games.
65 | */
66 | vm.youtubePlayerTime = 0;
67 | console.log("TIME IS 0");
68 | QUnit.deepEqual(vm.currentScore, {
69 | home: 0,
70 | away: 0
71 | }, 'Scores should zero at the beginning');
72 |
73 |
74 | vm.youtubePlayerTime = Infinity;
75 | console.log("TIME IS INFINITY");
76 | QUnit.deepEqual(
77 | vm.currentScore,
78 | vm.finalScore,
79 | 'At the end of the game, the current score is the final score'
80 | );
81 |
82 | /*
83 | NOTE: this is a bad test because the home/away numbers are
84 | not described or easily inferred here.
85 |
86 | Given the current fixture data, we are summing like this:
87 | | Time | Home Points | Away Points |
88 | | 0 | 0 | 0 |
89 | | 20 | 1 | 0 |
90 | | 40 | 3 | 0 |
91 | | 60 | 3 | 1 |
92 |
93 | Therefore at time=50, home=3 and away=0.
94 |
95 | TODO: move the testing data out of remote fixtures.
96 | */
97 | console.log("setting time to 50");
98 | vm.youtubePlayerTime = 50;
99 | console.log("set time");
100 | QUnit.deepEqual(vm.currentScore, {
101 | home: 3,
102 | away: 0
103 | }, 'Scores should reflect the sum for point stats');
104 |
105 | QUnit.start();
106 | });
107 | });
108 |
109 |
110 | QUnit.test('A stat can only be deleted by an admin', function () {
111 | var session = new Session({user: new User({ isAdmin: false }) });
112 |
113 | var vm = this.vm;
114 | vm.session = session;
115 | var frag = stache('')(vm);
116 |
117 | $('#qunit-fixture').html(frag);
118 |
119 | F.confirm(true);
120 |
121 | F('.stat-point .destroy-btn')
122 | .size(0, 'There is no destroy button')
123 | .then(function () {
124 | vm.session.user.isAdmin = true;
125 | ok(true, 'The user is given admin privileges');
126 | })
127 | .size(6, 'Destroy buttons are inserted')
128 | .click()
129 | .size(5, 'Clicking the destroy button removed a stat');
130 | });
131 |
132 |
133 | QUnit.test('Deleting a stat does not change playback location', function (assert) {
134 | var done = assert.async();
135 | var gotoCalled = false;
136 | var frag = stache('')({
137 | gameId: this.vm.gameId,
138 | session: new Session({
139 | user: new User({isAdmin: true})
140 | })
141 | });
142 |
143 | $('#qunit-fixture').html(frag);
144 |
145 |
146 | var vm = canViewModel($('game-details'));
147 | var gotoTimeMinus5 = vm.__proto__.gotoTimeMinus5; // jshint ignore:line
148 | vm.__proto__.gotoTimeMinus5 = function (){ // jshint ignore:line
149 | gotoCalled = true;
150 | };
151 |
152 | vm.on('game', function(ev, game) {
153 | F.confirm(true);
154 | F('.stat-point .destroy-btn')
155 | .exists('Destroy button exists')
156 | .click()
157 | .then(function () {
158 | notOk(gotoCalled, 'Seek was not called');
159 | vm.__proto__.gotoTimeMinus5 = gotoTimeMinus5; // jshint ignore:line
160 | done();
161 | });
162 | });
163 | });
164 |
--------------------------------------------------------------------------------
/public/components/game/details/test.html:
--------------------------------------------------------------------------------
1 | <game-details> tests
2 |
5 |
6 |
--------------------------------------------------------------------------------
/public/components/navigation/img/bitballs-logo-01.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/components/navigation/navigation.html:
--------------------------------------------------------------------------------
1 | <bitballs-navigation>
2 |
3 |
7 |
8 |
13 |
--------------------------------------------------------------------------------
/public/components/navigation/navigation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {Module} bitballs/components/navigation
3 | * @parent bitballs.components
4 | *
5 | * @group bitballs/components/navigation.properties 0 properties
6 | *
7 | * @description Provides navigation between different parts of the app
8 | * and lets a user login or logout.
9 | *
10 | * @signature ``
11 | * Creates the navigation for Bitballs.
12 | *
13 | * @param {bitballs/app} app The application viewModel. This component
14 | * will read and set the `session` property on the [bitballs/app].
15 | *
16 | *
17 | * @body
18 | *
19 | * To create a `` element pass the [bitballs/models/session]
20 | * and a [bitballs/models/game] id like:
21 | *
22 | * ```
23 | *
25 | * ```
26 | *
27 | * ## Example
28 | *
29 | * @demo public/components/navigation/navigation.html
30 | *
31 | */
32 | import { Component, DefineMap } from "can";
33 | import Session from "bitballs/models/session";
34 | import User from "bitballs/models/user";
35 | import view from "./navigation.stache";
36 | import $ from "jquery";
37 | steal.loader.global.jQuery = $;
38 |
39 | import "bootstrap/dist/css/bootstrap.css";
40 | import "bootstrap/js/dropdown";
41 | import "./navigation.less";
42 |
43 |
44 | var ViewModel = DefineMap.extend('NavigationVM',
45 | {
46 | /**
47 | * @property {bitballs/app} bitballs/components/navigation.app app
48 | * @parent bitballs/components/navigation.properties
49 | *
50 | * The [bitballs/app] used to add or destroy the session.
51 | */
52 | app: 'any',
53 | /**
54 | * @property {Promise} bitballs/components/navigation.sessionPromise sessionPromise
55 | * @parent bitballs/components/navigation.properties
56 | *
57 | * The promise that resolves when the user is logged in.
58 | */
59 | sessionPromise: 'any',
60 | /**
61 | * @property {bitballs/models/session} bitballs/models/session session
62 | *
63 | * Current session for the app
64 | */
65 | session: Session,
66 | /**
67 | * @property {bitballs/models/session} bitballs/components/navigation.loginSession loginSession
68 | * @parent bitballs/components/navigation.properties
69 | *
70 | * A placeholder session with a nested [bitballs/models/user user] property that
71 | * is used for two-way binding the login form's username and password.
72 | */
73 | loginSession: {
74 | default: function(){
75 | return new Session({user: new User()});
76 | }
77 | },
78 | /**
79 | * @function createSession
80 | *
81 | * Creates the session on the server and when successful updates [bitballs/components/navigation.app]
82 | * with the session. Sets [bitballs/components/navigation.sessionPromise].
83 | * @param {Event} [ev] Optional DOM event that will be prevented if passed.
84 | */
85 | createSession: function(ev){
86 | if(ev) {
87 | ev.preventDefault();
88 | }
89 | var self = this;
90 | var sessionPromise = this.loginSession.save().then(function(session){
91 | self.loginSession = new Session({user: new User()});
92 | self.app.session = session;
93 | });
94 | this.sessionPromise = sessionPromise;
95 | },
96 | /**
97 | * @function logout
98 | *
99 | * Destroys [bitballs/components/navigation.app]'s [bitballs/models/session] and
100 | * then removes it from the session.
101 | */
102 | logout: function(){
103 | var sessionPromise = this.app.session.destroy();
104 | this.sessionPromise = sessionPromise;
105 | this.app.session = null;
106 | },
107 | /**
108 | * @function closeDropdown
109 | * Closes the dropdown. Needed for when someone clicks on register.
110 | */
111 | closeDropdown: function ( el ) {
112 | $( el ).closest( ".session-menu" ).find( ".open .dropdown-toggle" ).dropdown( "toggle" );
113 | }
114 | });
115 |
116 | const Navigation = Component.extend({
117 | tag: "bitballs-navigation",
118 | view,
119 | ViewModel
120 | });
121 |
122 | export { Navigation, Navigation as Component, ViewModel };
123 |
--------------------------------------------------------------------------------
/public/components/navigation/navigation.less:
--------------------------------------------------------------------------------
1 | bitballs-navigation {
2 | .nav > li {
3 | float: left;
4 | }
5 |
6 | .navbar-nav {
7 | margin: 0;
8 | float: left;
9 |
10 | > li {
11 |
12 | > a {
13 | padding-top: 15px;
14 | padding-bottom: 15px;
15 | }
16 |
17 | > .dropdown-menu {
18 | position: absolute !important;
19 | top: 100%;
20 | right: 0;
21 | left: auto;
22 | background-color: #fff !important;
23 | border-radius: 4px !important;
24 | border: 1px solid rgba(0, 0, 0, .15) !important;
25 | box-shadow: 0 6px 12px rgba(0, 0, 0, .175) !important;
26 |
27 | form {
28 | padding: 10px;
29 | margin-bottom: 0px;
30 | }
31 |
32 | input {
33 | width: 150px
34 | }
35 | }
36 | }
37 | }
38 |
39 | .navbar-right {
40 | float: right;
41 | }
42 |
43 | .navbar {
44 | border-radius: none;
45 | }
46 |
47 | .navbar.navbar-default {
48 | background-color: #c46d3d;
49 | border: none;
50 | border-top-left-radius: 0;
51 | border-top-right-radius: 0;
52 | }
53 |
54 | .navbar-default .navbar-nav > li > a {
55 | color: white;
56 | }
57 |
58 | .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus, .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus {
59 | background-color: rgba(45, 49, 53, .3);
60 | color: white;
61 | }
62 |
63 | .main-logo {
64 | padding: 15px;
65 | background: url(./img/bitballs-logo-01.svg) no-repeat 0 50%;
66 | background-size: 90% auto;
67 | width: 150px;
68 | text-indent: -9999px;
69 | overflow: hidden;
70 | }
71 |
72 | .dropdown-menu form {
73 | padding: 5px;
74 | margin-bottom: 0px;
75 | }
76 | }
--------------------------------------------------------------------------------
/public/components/navigation/navigation.stache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
78 |
--------------------------------------------------------------------------------
/public/components/navigation/navigation_test.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import stache from 'can-stache';
3 | import QUnit from 'steal-qunit';
4 | import F from 'funcunit';
5 | import testUtils from 'bitballs/test/utils';
6 | import './navigation';
7 |
8 | F.attach(QUnit);
9 |
10 | QUnit.module('components/navigation/', {
11 | beforeEach: function () {
12 | var frag = stache('')();
13 | testUtils.insertAndPopulateIframe('#qunit-fixture', frag);
14 | },
15 | afterEach: function () {
16 | $('#qunit-fixture').empty();
17 | }
18 | });
19 |
20 | QUnit.test('Layout preserved at smaller screen resolutions', function (assert) {
21 | var evaluateAtWidth = function (resolution) {
22 | // Set the width
23 | F('#qunit-fixture iframe').then(function () {
24 |
25 | // For some reason the query needs to be redone
26 | $(this.selector).css('width', resolution);
27 | });
28 |
29 | // Confirm the styles
30 | F('.session-menu')
31 | .visible('Session menu is visible at ' + resolution)
32 | .css('float', 'right',
33 | 'Session menu is floated right at ' + resolution);
34 | F('.main-menu')
35 | .visible('Main menu is visible at ' + resolution)
36 | .css('float', 'left',
37 | 'Main menu is floated left at ' + resolution);
38 | };
39 |
40 | evaluateAtWidth('1170px');
41 | evaluateAtWidth('750px');
42 | });
43 |
44 | QUnit.test('Register button exists', function () {
45 | var frag = stache('')();
46 | var buttons = $(frag).find('.register-btn');
47 |
48 | QUnit.equal(buttons.length, 1, 'Register button found');
49 | });
50 |
--------------------------------------------------------------------------------
/public/components/navigation/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/public/components/player/details/details-test.js:
--------------------------------------------------------------------------------
1 | import QUnit from 'steal-qunit';
2 | import F from 'funcunit';
3 | import {ViewModel} from './details';
4 | import { fixture } from 'can';
5 | import stats from '../../../models/fixtures/stats';
6 |
7 | F.attach(QUnit);
8 |
9 | // ViewModel unit tests
10 | QUnit.module('bitballs/components/player/details', {
11 | beforeEach: function(){
12 | localStorage.clear();
13 | this.stats = stats;
14 | }
15 | });
16 |
17 | // Make sure we properly map stats to game ID's
18 | QUnit.test('should map stats to game ID', function (assert) {
19 | const GAME_ID = 40;
20 | const STATS = [{"id":3,"gameId":1,"playerId":3,"type":"1PA","time":43,"value":null},{"id":7,"gameId":1,"playerId":3,"type":"To","time":97,"value":null},{"id":27,"gameId":1,"playerId":3,"type":"2P","time":449,"value":null},{"id":37,"gameId":1,"playerId":3,"type":"1PA","time":723,"value":null},{"id":48,"gameId":1,"playerId":3,"type":"1PA","time":908,"value":null},{"id":53,"gameId":1,"playerId":3,"type":"1P","time":1066,"value":null},{"id":58,"gameId":1,"playerId":3,"type":"1PA","time":1175,"value":null},{"id":490,"gameId":15,"playerId":3,"type":"1PA","time":74,"value":null},{"id":526,"gameId":15,"playerId":3,"type":"DRB","time":378,"value":null},{"id":539,"gameId":15,"playerId":3,"type":"DRB","time":512,"value":null},{"id":540,"gameId":15,"playerId":3,"type":"1PA","time":519,"value":null},{"id":557,"gameId":15,"playerId":3,"type":"ORB","time":629,"value":null},{"id":561,"gameId":15,"playerId":3,"type":"DRB","time":640,"value":null},{"id":566,"gameId":15,"playerId":3,"type":"ORB","time":702,"value":null},{"id":567,"gameId":15,"playerId":3,"type":"Ast","time":706,"value":null},{"id":570,"gameId":15,"playerId":3,"type":"Ast","time":744,"value":null},{"id":583,"gameId":15,"playerId":3,"type":"ORB","time":810,"value":null},{"id":584,"gameId":15,"playerId":3,"type":"1PA","time":811,"value":null},{"id":936,"gameId":20,"playerId":3,"type":"DRB","time":318,"value":null},{"id":951,"gameId":20,"playerId":3,"type":"DRB","time":402,"value":null},{"id":953,"gameId":20,"playerId":3,"type":"1P","time":409,"value":null},{"id":1148,"gameId":23,"playerId":3,"type":"DRB","time":8,"value":null},{"id":1153,"gameId":23,"playerId":3,"type":"DRB","time":63,"value":null},{"id":1158,"gameId":23,"playerId":3,"type":"DRB","time":85,"value":null},{"id":1159,"gameId":23,"playerId":3,"type":"1P","time":88,"value":null},{"id":1192,"gameId":23,"playerId":3,"type":"1PA","time":343,"value":null},{"id":1195,"gameId":23,"playerId":3,"type":"DRB","time":351,"value":null},{"id":1202,"gameId":23,"playerId":3,"type":"1P","time":397,"value":null},{"id":1920,"gameId":35,"playerId":3,"type":"DRB","time":150,"value":null},{"id":1945,"gameId":35,"playerId":3,"type":"DRB","time":389,"value":null},{"id":1947,"gameId":35,"playerId":3,"type":"ORB","time":399,"value":null},{"id":1949,"gameId":35,"playerId":3,"type":"ORB","time":406,"value":null},{"id":1956,"gameId":35,"playerId":3,"type":"DRB","time":471,"value":null},{"id":1961,"gameId":35,"playerId":3,"type":"2PA","time":488,"value":null},{"id":1964,"gameId":35,"playerId":3,"type":"DRB","time":499,"value":null},{"id":1971,"gameId":35,"playerId":3,"type":"1P","time":571,"value":null},{"id":2283,"gameId":38,"playerId":3,"type":"1PA","time":61,"value":null},{"id":2291,"gameId":38,"playerId":3,"type":"1PA","time":108,"value":null},{"id":2304,"gameId":38,"playerId":3,"type":"DRB","time":246,"value":null},{"id":2308,"gameId":38,"playerId":3,"type":"DRB","time":266,"value":null},{"id":2310,"gameId":38,"playerId":3,"type":"ORB","time":277,"value":null},{"id":2311,"gameId":38,"playerId":3,"type":"1P","time":279,"value":null},{"id":2330,"gameId":38,"playerId":3,"type":"2PA","time":442,"value":null},{"id":2336,"gameId":38,"playerId":3,"type":"Blk","time":489,"value":null},{"id":2341,"gameId":38,"playerId":3,"type":"ORB","time":590,"value":null},{"id":2343,"gameId":38,"playerId":3,"type":"2P","time":596,"value":null},{"id":2346,"gameId":38,"playerId":3,"type":"1PA","time":625,"value":null},{"id":2350,"gameId":38,"playerId":3,"type":"ORB","time":652,"value":null},{"id":2373,"gameId":38,"playerId":3,"type":"DRB","time":842,"value":null},{"id":2377,"gameId":38,"playerId":3,"type":"2PA","time":894,"value":null},{"id":2385,"gameId":38,"playerId":3,"type":"To","time":954,"value":null},{"id":2639,"gameId":40,"playerId":3,"type":"1PA","time":42,"value":null}];
21 | const WANTED_STATS = STATS.filter(({gameId}) => gameId === GAME_ID);
22 |
23 | fixture({
24 | 'GET /services/games': {
25 | "data":[
26 | {
27 | "id": GAME_ID,
28 | "tournamentId":1,
29 | "round":"Semi Finals",
30 | "court":"2",
31 | "videoUrl":"is2Z6JU6nGg",
32 | "homeTeamId":18,
33 | "awayTeamId":2
34 | },
35 | ]
36 | },
37 | 'GET /services/stats': {
38 | "data": STATS
39 | },
40 | });
41 |
42 | let done = assert.async();
43 | let vm = new ViewModel({
44 | playerId: '1'
45 | });
46 |
47 | vm.on('statsByTournament', function(e, val){
48 | assert.deepEqual(val[1].serialize(), WANTED_STATS);
49 | done();
50 | });
51 | });
--------------------------------------------------------------------------------
/public/components/player/details/details.html:
--------------------------------------------------------------------------------
1 |
5 |
7 |
--------------------------------------------------------------------------------
/public/components/player/details/details.js:
--------------------------------------------------------------------------------
1 | import { Component, DefineMap } from 'can';
2 | import './details.less';
3 | import view from './details.stache';
4 | import Game from 'bitballs/models/game';
5 | import Player from 'bitballs/models/player';
6 | import Stat from 'bitballs/models/stat';
7 | import Tournament from 'bitballs/models/tournament';
8 |
9 | export const ViewModel = DefineMap.extend({
10 | /**
11 | * @property {Promise} bitballs/components/player/details.playerPromise playerPromise
12 | * @parent bitballs/components/player/details.properties
13 | *
14 | * A promise that fetches a [bitballs/models/player player] based on
15 | * [bitballs/components/player/details.ViewModel.prototype.playerId playerId].
16 | **/
17 | get playerPromise() {
18 | return Player.get(this.playerId);
19 | },
20 | /**
21 | * @property {bitballs/models/player} bitballs/components/player/details.player player
22 | * @parent bitballs/components/player/details.properties
23 | *
24 | * A [bitballs/models/player player] instance.
25 | **/
26 | player: {
27 | get: function(lastSet, setVal){
28 | this.playerPromise.then(setVal);
29 | }
30 | },
31 | /**
32 | * @property {Promise} bitballs/components/player/details.tournamentPromise tournamentsPromise
33 | * @parent bitballs/components/player/details.properties
34 | *
35 | * A promise that fetches a [bitballs/models/tournament.static.List tournament List] based on
36 | * [bitballs/components/player/details.ViewModel.prototype.playerId playerId].
37 | **/
38 | get tournamentsPromise() {
39 | return Tournament.getList();
40 | },
41 | /**
42 | * @property {bitballs/models/tournament.static.List} bitballs/components/player/details.tournament tournament
43 | * @parent bitballs/components/player/details.properties
44 | *
45 | * A [bitballs/models/tournament.static.List tournament List] instance.
46 | **/
47 | tournaments: {
48 | get: function(lastSet, setVal){
49 | this.tournamentsPromise.then(setVal);
50 | }
51 | },
52 | /**
53 | * @property {Promise} bitballs/components/player/details.gamePromise gamesPromise
54 | * @parent bitballs/components/player/details.properties
55 | *
56 | * A promise that fetches a [bitballs/models/game.static.List game List] based on
57 | * [bitballs/components/player/details.ViewModel.prototype.playerId playerId].
58 | **/
59 | get gamesPromise() {
60 | return Game.getList();
61 | },
62 | /**
63 | * @property {bitballs/models/game.static.List} bitballs/components/player/details.game game
64 | * @parent bitballs/components/player/details.properties
65 | *
66 | * A [bitballs/models/game.static.List game List] instance.
67 | **/
68 | games: {
69 | get: function(lastSet, setVal){
70 | this.gamesPromise.then(setVal);
71 | }
72 | },
73 | statTypes: {
74 | default: () => Stat.statTypes
75 | },
76 | /**
77 | * @property {Promise} bitballs/components/player/details.statPromise statsPromise
78 | * @parent bitballs/components/player/details.properties
79 | *
80 | * A promise that fetches a [bitballs/models/stat.static.List stat List] based on
81 | * [bitballs/components/player/details.ViewModel.prototype.playerId playerId].
82 | **/
83 | get statsPromise() {
84 | return Stat.getList({
85 | where: {playerId: this.playerId},
86 | withRelated: [
87 | 'game.tournament'
88 | ]
89 | });
90 | },
91 | /**
92 | * @property {bitballs/models/stat.static.List} bitballs/components/player/details.stat stat
93 | * @parent bitballs/components/player/details.properties
94 | *
95 | * A [bitballs/models/stat.static.List stat List] instance.
96 | **/
97 | stats: {
98 | get: function(lastSet, setVal){
99 | this.statsPromise.then(setVal);
100 | }
101 | },
102 |
103 | get tournamentStats() {
104 | if (!this.stats) {
105 | return null;
106 | }
107 |
108 | let playerTournaments = [];
109 | this.stats.forEach((stat) => {
110 | let statTournament = stat.game.tournament;
111 | statTournament.year = new Date(statTournament.date).getFullYear();
112 |
113 | let tournament = playerTournaments.find((tournament) => tournament.id === statTournament.id);
114 | let statModel = new Stat(stat);
115 | if(tournament) {
116 | tournament.stats.push(statModel);
117 | }
118 | else {
119 | statTournament.stats = new Stat.List([statModel]);
120 | playerTournaments.push(statTournament);
121 | }
122 | });
123 | return playerTournaments;
124 | },
125 |
126 | get statsByTournament() {
127 | if (!this.games || !this.stats || !this.tournaments) {
128 | return null;
129 | }
130 |
131 | let mapGamesToTournaments = {};
132 | this.games.forEach(({ id, tournamentId }) => {
133 | mapGamesToTournaments[id] = tournamentId;
134 | });
135 |
136 | let statsByTournament = [];
137 |
138 | this.stats.forEach((stat) => {
139 | let tournamentId = mapGamesToTournaments[stat.gameId];
140 | if(tournamentId){
141 | if (!statsByTournament[tournamentId]) {
142 | statsByTournament[tournamentId] = new Stat.List();
143 | }
144 |
145 | statsByTournament[tournamentId].push(stat);
146 | }
147 | });
148 | return statsByTournament;
149 | },
150 | });
151 |
152 | export const PlayerDetails = Component.extend({
153 | tag: 'player-details',
154 | ViewModel,
155 | view
156 | });
157 |
158 | export { PlayerDetails as Component };
159 |
--------------------------------------------------------------------------------
/public/components/player/details/details.less:
--------------------------------------------------------------------------------
1 | @border: 1px solid #ddd;
2 |
3 | player-details {
4 | display: block;
5 | h1 {
6 | margin-bottom: 20px;
7 | }
8 | h1, h2 {
9 | font-family: "Helvetica Neue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif
10 | }
11 | .player-stats {
12 | li {
13 | border-left: @border;
14 | width: 140px;
15 | padding: 0 20px;
16 | text-align: center;
17 | &:last-child {
18 | border-right: @border;
19 | }
20 | }
21 | }
22 |
23 | .tournament-stats {
24 | border-bottom: @border;
25 | padding-bottom: 10px;
26 | li {
27 | padding: 0 35px 10px 0;
28 | p {
29 | margin-bottom: 0;
30 | }
31 | }
32 | }
33 |
34 | .player-stats, .tournament-stats {
35 | display: flex;
36 | flex-direction: row;
37 | padding-left: 0;
38 | list-style: none;
39 | margin-bottom: 30px;
40 | li {
41 | p {
42 | font-size: 16px;
43 | font-weight: 400;
44 | color: #c46d3d;
45 | }
46 | div {
47 | font-size: 30px;
48 | font-weight: 500;
49 | }
50 | }
51 | }
52 | span.stats {
53 | padding: 10px 15px;
54 | }
55 | table.stats {
56 | border: solid 1px #000;
57 | th, td {
58 | padding: 10px 15px;
59 | border: solid 1px #000;
60 | }
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/public/components/player/details/details.md:
--------------------------------------------------------------------------------
1 | @parent bitballs
2 | @module {can.Component} bitballs/components/player/details
3 |
4 | A short description of the player-details component
5 |
6 | @signature ``
7 |
8 | @body
9 |
10 | ## Use
11 |
12 |
--------------------------------------------------------------------------------
/public/components/player/details/details.stache:
--------------------------------------------------------------------------------
1 | {{# player }}
2 | {{ name }}
3 |
4 | -
5 |
{{ age }}
6 | Age
7 |
8 | -
9 |
{{ weight }}lbs
10 | Weight
11 |
12 | -
13 |
{{ height }}"
14 | Height
15 |
16 |
17 | {{/ player }}
18 |
19 | Overall Stats
20 | {{# for(statType of stats.aggregated) }}
21 |
22 | {{statType.name}}: {{statType.default}}
23 | {{/ for }}
24 |
25 | Stats by Year
26 |
27 |
28 |
29 | Year |
30 | {{# for(statType of stats.aggregated) }}
31 | {{statType.name}} |
32 | {{/ for }}
33 |
34 | {{# for(tournament of tournamentStats) }}
35 |
36 | {{ tournament.year }} |
37 | {{# for(stat of tournament.stats.aggregated)}}
38 | {{stat.default}} |
39 | {{/ for}}
40 |
41 | {{/ for}}
42 |
43 |
--------------------------------------------------------------------------------
/public/components/player/details/test.html:
--------------------------------------------------------------------------------
1 | bitballs/components/player/details
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/components/player/edit/edit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
16 |
--------------------------------------------------------------------------------
/public/components/player/edit/edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {Module} bitballs/components/player/edit
3 | * @parent bitballs.components
4 | *
5 | * @group bitballs/components/player/edit.properties 0 properties
6 | *
7 | * @description Provides an interface for editing the values of a
8 | * [bitballs/models/player] model.
9 | *
10 | * @signature ``
11 | * Creates a form with inputs for each property in a [bitballs/models/player] model.
12 | *
13 | * @param {Boolean} is-admin Configures whether or not admin specific
14 | * features are enabled.
15 | *
16 | *
17 | * @body
18 | *
19 | * To create a `` element pass a boolean like [bitballs/app.prototype.isAdmin]:
20 | *
21 | * ```
22 | *
24 | * ```
25 | *
26 | * ## Example
27 | *
28 | * @demo public/components/player/edit/edit.html
29 | *
30 | **/
31 | import { Component, DefineMap } from "can";
32 | import Player from "bitballs/models/player";
33 | import view from "./edit.stache";
34 | import "bootstrap/dist/css/bootstrap.css";
35 |
36 |
37 | export const ViewModel = DefineMap.extend("PlayerEditVM",
38 | {
39 | /**
40 | * @property {Boolean} bitballs/components/player/edit.isAdmin isAdmin
41 | * @parent bitballs/components/player/edit.properties
42 | *
43 | * Configures whether or not admin specific features are enabled.
44 | **/
45 | isAdmin: {
46 | type: 'boolean',
47 | default: false
48 | },
49 | /**
50 | * @property {bitballs/models/player} bitballs/components/player/edit.player player
51 | * @parent bitballs/components/player/edit.properties
52 | *
53 | * The model that will be bound to the form.
54 | **/
55 | player: {
56 | Type: Player,
57 | Default: Player
58 | },
59 | /**
60 | * @property {Boolean} bitballs/components/player/edit.isNewPlayer isNewPlayer
61 | * @parent bitballs/components/player/edit.properties
62 | *
63 | * Whether the player has not been created yet.
64 | */
65 | isNewPlayer: {
66 | get: function isNew() {
67 | return this.player.isNew();
68 | }
69 | },
70 | /**
71 | * @property {Promise} bitballs/components/player/edit.savePromise savePromise
72 | * @parent bitballs/components/player/edit.properties
73 | *
74 | * A [bitballs/models/player] model.
75 | */
76 | savePromise: 'any',
77 | /**
78 | * @function savePlayer
79 | *
80 | * Creates/updates the player on the server and when successful sets [bitballs/components/player/edit.player]
81 | * to a new [bitballs/models/player] model. Fires a "saved" event.
82 | *
83 | * @param {Event} [ev] A DOM Level 2 event that [`preventDefault`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
84 | * will be called on.
85 | *
86 | * @return {Promise}
87 | */
88 | savePlayer: function(ev){
89 | if (ev) {
90 | ev.preventDefault();
91 | }
92 |
93 | var self = this;
94 | var player = this.player;
95 | var promise;
96 |
97 | if(player.isNew()) {
98 | promise = player.save().then(function(){
99 | self.player = new Player();
100 | });
101 | } else {
102 | promise = player.save();
103 | }
104 |
105 | promise.then(function(){
106 | player.backup();
107 | self.dispatch("saved");
108 | });
109 |
110 | this.savePromise = promise;
111 |
112 | return promise;
113 | },
114 | /**
115 | * @function cancel
116 | *
117 | * Restores the [bitballs/models/player] model to its state prior to editing.
118 | * Fires a "canceled" event.
119 | */
120 | cancel: function() {
121 | this.player = this.player.restore();
122 | this.dispatch("canceled");
123 | }
124 | });
125 |
126 | export const PlayerEdit = Component.extend({
127 | tag: "player-edit",
128 | view,
129 | ViewModel
130 | });
131 |
132 | export { PlayerEdit as Component };
133 |
--------------------------------------------------------------------------------
/public/components/player/edit/edit.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donejs/bitballs/9670ae729d4cbfe0900d20e654d2bf96aa402c6a/public/components/player/edit/edit.md
--------------------------------------------------------------------------------
/public/components/player/edit/edit.stache:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{# if(isAdmin) }}
4 |
52 | {{/ if }}
53 |
--------------------------------------------------------------------------------
/public/components/player/edit/edit_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from 'steal-qunit';
2 | import { DefineMap, stache } from "can";
3 | import { ViewModel } from 'bitballs/components/player/edit/edit';
4 | import Player from 'bitballs/models/player';
5 | import F from 'funcunit';
6 | import $ from "jquery";
7 | import './edit';
8 |
9 | import defineFixtures from 'bitballs/models/fixtures/players';
10 |
11 | F.attach(QUnit);
12 |
13 | // viewmodel unit tests
14 | QUnit.module('components/player/edit/', function(hooks){
15 |
16 | hooks.beforeEach(function(){
17 | localStorage.clear();
18 | defineFixtures();
19 | });
20 |
21 |
22 |
23 | QUnit.test('Tests are running', function(assert){
24 | assert.ok( true, "Passed!" );
25 | });
26 |
27 | QUnit.test('Can create new ViewModel', function(assert){
28 | var vm = new ViewModel();
29 | vm.player.name = "Justin";
30 | assert.ok( !!vm , "Passed!" );
31 | });
32 |
33 | QUnit.test("Create player", function(assert){
34 | assert.expect(1);
35 | var done = assert.async(),
36 | player = {
37 | "name": "Test Player",
38 | "weight": 200,
39 | "height": 71,
40 | "birthday": "1980-01-01"
41 | },
42 | playerModel = new Player(player),
43 | vm = new ViewModel({
44 | player:playerModel
45 | });
46 |
47 | vm.on("saved", function(){
48 | player.id = 1;
49 | assert.deepEqual(player, playerModel.get(), "New player saved");
50 | vm.unbind("saved");
51 | done();
52 | });
53 | vm.savePlayer();
54 |
55 | });
56 |
57 | QUnit.test("Create player fails without name", function(assert){
58 | assert.expect(2);
59 | var done = assert.async(),
60 | player = {
61 | "weight": 200,
62 | "height": 71,
63 | "birthday": "1980-01-01"
64 | },
65 | playerModel = new Player(player),
66 | vm = new ViewModel({
67 | player: playerModel
68 | });
69 |
70 | vm.savePlayer();
71 | vm.savePromise.then(function(resp, type) {
72 | done();
73 | }, function(resp) {
74 | assert.equal(resp.status, '400');
75 | assert.equal(resp.statusText, 'error');
76 | done();
77 | });
78 | });
79 |
80 | QUnit.test("Update player", function(assert){
81 | assert.expect(1);
82 | var done = assert.async(),
83 | player = {
84 | "name": "Test Player",
85 | "weight": 200,
86 | "height": 71,
87 | "birthday": "1980-01-01",
88 | "id": 1
89 | },
90 | playerModel = new Player(player),
91 | vm = new ViewModel({
92 | player:playerModel
93 | });
94 |
95 | //update player info
96 | vm.player.name = "Test Player (modified)";
97 |
98 | vm.on("saved", function(){
99 | player.name = "Test Player (modified)";
100 | assert.deepEqual(vm.player.name, player.name, "Player updated");
101 | vm.unbind("saved");
102 | done();
103 | });
104 |
105 | vm.savePlayer();
106 |
107 | });
108 |
109 | QUnit.test('Properties are restored when canceled', function (assert) {
110 | var initialName = 'Chris Gomez';
111 | var initialWeight = 175;
112 | var initialHeight = 69;
113 | var editedName = 'Alfred Hitchcock';
114 | var editedWeight = 210;
115 | var editedHeight = 67;
116 |
117 | var vm = new ViewModel({
118 | player: {
119 | name: initialName,
120 | weight: initialWeight,
121 | height: initialHeight
122 | }
123 | });
124 |
125 | var player = vm.player;
126 |
127 | player.backup();
128 |
129 | assert.equal(player.name, initialName, 'Initial name is correct');
130 | assert.equal(player.weight, initialWeight, 'Initial weight is correct');
131 | assert.equal(player.height, initialHeight, 'Initial height is correct');
132 |
133 | player.name = editedName;
134 | player.weight = editedWeight;
135 | player.height = editedHeight;
136 |
137 | assert.equal(player.name, editedName, 'Edited name is correct');
138 | assert.equal(player.weight, editedWeight, 'Edited weight is correct');
139 | assert.equal(player.height, editedHeight, 'Edited height is correct');
140 |
141 | vm.cancel();
142 |
143 | assert.equal(player.name, initialName, 'Restored name is correct');
144 | assert.equal(player.weight, initialWeight, 'Restored weight is correct');
145 | assert.equal(player.height, initialHeight, 'Restored height is correct');
146 | });
147 |
148 |
149 | QUnit.test('Form is only shown to admins', function () {
150 |
151 | var vm = new DefineMap({
152 | isAdmin: false
153 | });
154 | var frag = stache('')(vm);
155 |
156 | QUnit.equal($('player-edit .edit-form', frag).length, 0,
157 | 'Form is excluded for non-admin user');
158 |
159 | vm.isAdmin = true;
160 |
161 | QUnit.equal($('player-edit .edit-form', frag).length, 1,
162 | 'Form is included for admin user');
163 | });
164 | });
165 |
--------------------------------------------------------------------------------
/public/components/player/edit/test.html:
--------------------------------------------------------------------------------
1 | player/edit
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/components/player/list/list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/components/player/list/list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {Module} bitballs/components/player/list
3 | * @parent bitballs.components
4 | *
5 | * @group bitballs/components/player/list.properties 0 properties
6 | *
7 | * @description Provides links to the existing [bitballs/models/player]s. Enables logged
8 | * in admin users to create, update, and destroy [bitballs/models/player]s.
9 | *
10 | * @signature ``
11 | * Renders a list of [bitballs/models/player] models.
12 | *
13 | * @param {Boolean} is-admin Configures whether or not admin specific
14 | * features are enabled.
15 | *
16 | *
17 | * @body
18 | *
19 | * To create a `` element pass a boolean like [bitballs/app.prototype.isAdmin]:
20 | *
21 | * ```
22 | *
24 | * ```
25 | *
26 | * ## Example
27 | *
28 | * @demo public/components/player/list/list.html
29 | *
30 | **/
31 | import { Component, DefineMap } from "can";
32 | import Player from "bitballs/models/player";
33 | import view from "./list.stache";
34 | import "bootstrap/dist/css/bootstrap.css";
35 |
36 | export const ViewModel = DefineMap.extend('PlayerListVM',
37 | {
38 | /**
39 | * @property {Boolean} bitballs/components/player/list.isAdmin isAdmin
40 | * @parent bitballs/components/player/list.properties
41 | *
42 | * Configures whether or not admin specific features are enabled.
43 | **/
44 | isAdmin: {
45 | type: 'boolean',
46 | default: false
47 | },
48 | /**
49 | * @property {bitballs/models/Player} bitballs/models/player editingPlayer
50 | *
51 | * holds the current player instance that is being edited
52 | */
53 | editingPlayer: {Type: Player, default: null},
54 | /**
55 | * @property {Promise} bitballs/components/player/list.playersPromise playersPromise
56 | * @parent bitballs/components/player/list.properties
57 | *
58 | * A [bitballs/models/player] model List.
59 | */
60 | playersPromise: {
61 | default: function(){
62 | return Player.getList({orderBy: "name"});
63 | }
64 | },
65 | /**
66 | * @function editPlayer
67 | *
68 | * Selects a [bitballs/models/player] model for editing.
69 | *
70 | * @param {bitballs/models/player} player
71 | * The player model that will be passed to the ``
72 | * component.
73 | */
74 | editPlayer: function(player){
75 | player.backup();
76 | this.editingPlayer = player;
77 | },
78 | /**
79 | * @function removeEdit
80 | *
81 | * Deselects the [bitballs/models/player] model being edited.
82 | */
83 | removeEdit: function(){
84 | this.editingPlayer = null;
85 | },
86 | /**
87 | * @function
88 | * @description Delete a player from the database.
89 | * @param {bitballs/models/player} player The [bitballs/models/player] to delete.
90 | *
91 | * @body
92 | *
93 | * Use in a template like:
94 | * ```
95 | *
96 | * ```
97 | */
98 | deletePlayer: function (player) {
99 | if (! window.confirm('Are you sure you want to delete this player?')) {
100 | return;
101 | }
102 | player.destroy();
103 | }
104 | });
105 |
106 | export const PlayerList = Component.extend({
107 | tag: "player-list",
108 | view,
109 | ViewModel
110 | });
111 |
112 | export { PlayerList as Component };
113 |
--------------------------------------------------------------------------------
/public/components/player/list/list.stache:
--------------------------------------------------------------------------------
1 |
2 |
3 | Players
4 |
5 |
6 |
7 | Name |
8 | Age |
9 | Weight |
10 | Height |
11 | |
12 |
13 |
14 |
15 | {{# playersPromise.isPending }}
16 | Loading |
17 | {{/ playersPromise.isPending }}
18 | {{# if(playersPromise.isResolved) }}
19 | {{# each(playersPromise.value) }}
20 |
21 | {{# eq(this,../editingPlayer) }}
22 |
23 |
29 | |
30 | {{ else }}
31 | {{ name }} |
32 | {{ age }} |
33 | {{ weight }} |
34 | {{ height }} |
35 |
36 | {{# if(../isAdmin) }}
37 |
40 |
45 | {{/ if }}
46 | |
47 | {{/ eq }}
48 |
49 | {{ else }}
50 | No Players |
51 | {{/ each }}
52 | {{/ if }}
53 |
54 |
55 | {{# if(isAdmin) }}
56 |
57 | {{/ if }}
58 |
--------------------------------------------------------------------------------
/public/components/player/list/list_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from "steal-qunit";
2 | import Player from "bitballs/models/player";
3 | import { ViewModel } from "./list";
4 | import defineFixtures from "bitballs/models/fixtures/players";
5 | import F from "funcunit";
6 | import { fixture, stache } from "can";
7 | import $ from "jquery";
8 |
9 | F.attach(QUnit);
10 |
11 | var vm;
12 | QUnit.module("components/player/list/", {
13 | beforeEach: function () {
14 | localStorage.clear();
15 | fixture.delay = 1;
16 | defineFixtures();
17 | vm = new ViewModel();
18 | },
19 | afterEach: function () {
20 | defineFixtures();
21 | vm = undefined;
22 | }
23 | });
24 |
25 | QUnit.test("players property loads players from server during instantiation", function (assert) {
26 | var done = assert.async();
27 | vm.playersPromise.then(function (players) {
28 | assert.ok(players.length, "we got some players");
29 | done();
30 | });
31 | });
32 |
33 | QUnit.test("editPlayer sets editingPlayer to passed in player", function (assert) {
34 | var player = new Player({ name: "Ryan" });
35 | vm.editPlayer(player);
36 | assert.deepEqual(vm.editingPlayer, player, "editingPlayer was set");
37 | });
38 |
39 | QUnit.test("removeEdit removes editingPlayer", function (assert) {
40 | var player = { name: "Ryan" };
41 | vm.editingPlayer = player;
42 | vm.removeEdit();
43 | assert.notOk(vm.editingPlayer, "editingPlayer was removed");
44 | });
45 |
46 | QUnit.test('Loading message shown while players list is loaded', function (assert) {
47 | var frag = stache('')();
48 |
49 | var resolveFixture;
50 |
51 | $('#qunit-fixture').html(frag);
52 |
53 | fixture('GET /services/players', function (req, res) {
54 | resolveFixture = res;
55 | });
56 |
57 | F('tbody tr.info')
58 | .exists('Loading element is present')
59 | .text('Loading', 'Loading message is shown')
60 | .then(function () {
61 | assert.ok(true, 'Request is resolved');
62 | resolveFixture({ data: [] });
63 | })
64 | .closest('tbody')
65 | .size(0, 'Loading element was removed');
66 | });
67 |
68 | QUnit.test('Placeholder message is shown when player list is empty', function () {
69 | var frag = stache('')();
70 |
71 | // Make the players fixture return an empty list
72 | fixture('GET /services/players', function () {
73 | return { data: [] };
74 | });
75 |
76 | $('#qunit-fixture').html(frag);
77 |
78 | F('tbody tr.empty-list-placeholder')
79 | .exists('Placeholder element is present');
80 | });
81 |
--------------------------------------------------------------------------------
/public/components/player/list/test.html:
--------------------------------------------------------------------------------
1 | <player-list> tests
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/components/test.js:
--------------------------------------------------------------------------------
1 | import './game/details/details_test';
2 | // import './navigation/navigation_test'; // Commented out because this needs to attach funcunit to something else
3 | import './player/edit/edit_test';
4 | import './player/list/list_test';
5 | import './tournament/details/details_test';
6 | import './tournament/list/list_test';
7 | import './user/test';
8 | import './player/details/details-test';
9 |
--------------------------------------------------------------------------------
/public/components/tournament/details/details.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/public/components/tournament/details/details_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from 'steal-qunit';
2 | import { ViewModel } from './details';
3 | import defineTournamentFixtures from 'bitballs/models/fixtures/tournaments';
4 | import 'bitballs/models/fixtures/players';
5 | import defineGameFixtures from 'bitballs/models/fixtures/games';
6 | import clone from 'steal-clone';
7 | import { DefineMap, fixture } from "can";
8 | import Game from 'bitballs/models/game';
9 |
10 | var vm;
11 |
12 | QUnit.module('components/tournament/details/', {
13 | beforeEach: function (assert) {
14 | let done = assert.async();
15 | localStorage.clear();
16 | defineTournamentFixtures();
17 | defineGameFixtures();
18 |
19 |
20 | clone({
21 | 'bitballs/models/tournament': {
22 | 'default': {
23 | get: function() {
24 | console.log('we are here...');
25 | return Promise.resolve(new DefineMap({
26 | name: 'Test Name'
27 | }));
28 | }
29 | },
30 | __useDefault: true
31 | }
32 | })
33 | .import('./details')
34 | .then(({ ViewModel }) => {
35 | vm = new ViewModel({
36 | tournamentId: 2
37 | });
38 | done();
39 | });
40 | }
41 | });
42 |
43 | QUnit.test('should load a tournament', (assert) => {
44 | let done = assert.async();
45 | vm.on('tournament', function (ev, newVal) {
46 | assert.equal(newVal.name, 'Test Name', 'with the correct name' );
47 | done();
48 | });
49 | });
50 |
51 | QUnit.test('The selected round defaults to the first available round', function () {
52 | var vm = new ViewModel();
53 |
54 | var gamesResponse = { data: [] };
55 |
56 | Game.courtNames.forEach(function (courtName) {
57 | gamesResponse.data.push({
58 | round: Game.roundNames[0],
59 | court: courtName
60 | });
61 | });
62 |
63 | fixture('/services/games', function() {
64 | return gamesResponse;
65 | });
66 |
67 | vm.on("selectedRound", function(){});
68 | QUnit.stop();
69 | vm.gamesPromise.then(function (games) {
70 | QUnit.start();
71 | QUnit.equal(vm.selectedRound, Game.roundNames[1],
72 | 'The second round is selected');
73 | });
74 | });
75 |
76 | QUnit.test('The selected court defaults to the first available court', function () {
77 | var vm = new ViewModel();
78 |
79 | var gamesResponse = { data: [{
80 | round: Game.roundNames[0],
81 | court: Game.courtNames[0]
82 | }] };
83 |
84 | fixture('/services/games', function() {
85 | return gamesResponse;
86 | });
87 |
88 | QUnit.stop();
89 | vm.on("selectedCourt", function(){});
90 | vm.gamesPromise.then(function (games) {
91 | QUnit.start();
92 | vm.on('selectedCourt', function(){});
93 | QUnit.equal(vm.selectedCourt, Game.courtNames[1],
94 | 'The second court is selected');
95 | });
96 | });
97 |
--------------------------------------------------------------------------------
/public/components/tournament/details/test.html:
--------------------------------------------------------------------------------
1 | Tournament Details Tests
2 |
3 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/public/components/tournament/list/list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {Module} bitballs/components/tournament/list
3 | * @parent bitballs.components
4 | *
5 | * @group bitballs/components/tournament/list.properties 0 properties
6 | *
7 | * @description Provides links to the existing tournaments. Enables logged
8 | * in admin users to create and destroy tournaments.
9 | *
10 | * @signature ``
11 | * Renders a list of tournaments.
12 | *
13 | * @param {Boolean} is-admin Configures whether or not admin specific
14 | * features are enabled.
15 | *
16 | *
17 | * @body
18 | *
19 | * To create a `` element pass a boolean like [bitballs/app.prototype.isAdmin]:
20 | *
21 | * ```
22 | *
24 | * ```
25 | *
26 | * ## Example
27 | *
28 | * @demo public/components/tournament/list/list.html
29 | *
30 | */
31 | import {
32 | Component, DefineMap
33 | } from "can";
34 | import Tournament from "bitballs/models/tournament";
35 | import view from "./list.stache";
36 | import "bootstrap/dist/css/bootstrap.css!";
37 | import "can-stache-route-helpers";
38 |
39 | export const ViewModel = DefineMap.extend('TournamentList',
40 | /** @prototype */
41 | {
42 |
43 | tournamentsPromise: {
44 | default: function(){
45 | return Tournament.getList({orderBy: "date"});
46 | }
47 | },
48 | /**
49 | * @property {bitballs/models/tournament} bitballs/components/tournament/list.tournament tournament
50 | * @parent bitballs/components/tournament/list.properties
51 | *
52 | * The [bitballs/models/tournament] model that backs the tournament
53 | * creation form.
54 | **/
55 | tournament: {
56 | Type: Tournament,
57 | Default: Tournament
58 | },
59 | /**
60 | * @property {Boolean} bitballs/components/tournament/list.isAdmin isAdmin
61 | * @parent bitballs/components/tournament/list.properties
62 | *
63 | * Configures whether or not admin specific features are enabled.
64 | **/
65 | isAdmin: {
66 | type: 'boolean',
67 | default: false
68 | },
69 | /**
70 | * @property {Promise} bitballs/components/tournament/list.savePromise savePromise
71 | * @parent bitballs/components/tournament/list.properties
72 | *
73 | * A promise that resolves when [bitballs/component/tournament/list.prototype.createTournament]
74 | * is called and the [bitballs/models/tournament] model is persisted to the server.
75 | **/
76 | savePromise: 'any',
77 | /**
78 | * @function createTournament
79 | *
80 | * @description Creates the tournament on the server and when successful sets
81 | * [bitballs/components/tournament/list.tournament] to a new [bitballs/models/tournament] model.
82 | *
83 | * @param {Event} [ev] A DOM Level 2 event.
84 | *
85 | * @return {Promise} A [bitballs/models/tournament] model.
86 | */
87 | createTournament: function(ev) {
88 | if (ev) {
89 | ev.preventDefault();
90 | }
91 | var self = this;
92 |
93 | var promise = this.tournament.save().then(function(player) {
94 | self.tournament = new Tournament();
95 | });
96 |
97 | this.savePromise = promise;
98 | return promise;
99 | },
100 | /**
101 | * @function
102 | * @description Delete a tournament from the database.
103 | * @param {bitballs/models/tournament} tournament The [bitballs/models/tournament] to delete.
104 | *
105 | * @body
106 | *
107 | * Use in a template like:
108 | * ```
109 | *
110 | * ```
111 | */
112 | deleteTournament: function (tournament) {
113 | if (! window.confirm('Are you sure you want to delete this tournament?')) {
114 | return;
115 | }
116 | tournament.destroy();
117 | }
118 | });
119 |
120 | export const TournamentList = Component.extend({
121 | tag: "tournament-list",
122 | view,
123 | ViewModel
124 | });
125 |
126 | export { TournamentList as Component };
127 |
--------------------------------------------------------------------------------
/public/components/tournament/list/list.stache:
--------------------------------------------------------------------------------
1 | Tournaments
2 |
3 | {{# if(tournamentsPromise.isPending) }}
4 | Loading
5 | {{/ if }}
6 | {{# if(tournamentsPromise.isResolved) }}
7 | {{# each(tournamentsPromise.value) }}
8 | {{ year }}
9 | {{# if(../isAdmin) }}
10 |
15 | {{/ if }}
16 |
17 | {{ else }}
18 | No Tournaments
19 | {{/ each }}
20 | {{/ if }}
21 |
22 | {{# if(isAdmin) }}
23 | New Tournament
24 |
41 | {{/ if }}
42 |
--------------------------------------------------------------------------------
/public/components/tournament/list/list_test.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import { stache, fixture } from "can";
3 | import QUnit from 'steal-qunit';
4 | import F from 'funcunit';
5 | import { ViewModel } from './list';
6 | import defineFixtures from 'bitballs/models/fixtures/tournaments';
7 |
8 | F.attach(QUnit);
9 |
10 | QUnit.module('components/tournament/list/', {
11 | beforeEach: function () {
12 | localStorage.clear();
13 | fixture.delay = 1;
14 | defineFixtures();
15 | }
16 | });
17 |
18 | QUnit.test('creating tournament fails without a name', function(assert){
19 | var done = assert.async();
20 |
21 | assert.expect(2);
22 |
23 | var vm = new ViewModel();
24 |
25 | vm.createTournament();
26 | vm.savePromise.then(done, function(resp, type){
27 | assert.equal(resp.statusText, 'error', 'fail creation without date');
28 | assert.equal(resp.status, '400', 'rejected');
29 | done();
30 | });
31 | });
32 |
33 | QUnit.test('Create button is disabled while posting data', function (assert) {
34 | var done = assert.async();
35 | var expectingRequest = true;
36 | var vm = new ViewModel({
37 | app: {
38 | isAdmin: true
39 | },
40 | tournament: {
41 | name: 'Ballderdash',
42 | date: '01/21/1987'
43 | }
44 | });
45 |
46 |
47 | var frag = stache('')(vm);
48 | var resolveRequest;
49 |
50 | fixture('POST /services/tournaments', function (req, res) {
51 | QUnit.ok(expectingRequest, 'Request was made');
52 |
53 | // Determine when the request resolves, later
54 | resolveRequest = res;
55 |
56 | // The request should only be made once
57 | expectingRequest = false;
58 | });
59 |
60 | $('#qunit-fixture').html(frag);
61 |
62 | // Click the button multiple times and ensure it's disabled
63 | // during requests
64 | F('tournament-list .create-btn')
65 | .visible('Create button is visible')
66 | .attr('disabled', undefined, 'Create button is enabled')
67 | .click();
68 | F('tournament-list .create-btn')
69 | .attr('disabled', 'disabled', 'Create button is disabled')
70 | .then(function() {
71 | resolveRequest({id: 9910911});
72 | })
73 | .attr('disabled', undefined,
74 | 'Create button is enabled after the request is resolved').then(function(){
75 | done();
76 | });
77 |
78 | });
79 |
--------------------------------------------------------------------------------
/public/components/tournament/list/test.html:
--------------------------------------------------------------------------------
1 | Tournament Tests
2 |
4 |
2 |
3 |
4 |
5 |
6 |
7 | {{# if(session.user) }}
8 | {{# unless(session.user.verified) }}
9 |
10 | User Verification
11 |
16 | {{/ unless }}
17 | {{/ if }}
18 |
19 |
20 |
21 |
65 |
--------------------------------------------------------------------------------
/public/components/user/details/details.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {Module} bitballs/components/user/details
3 | * @parent bitballs.components
4 | *
5 | * @description Provides a custom element that allows a user to
6 | * register, to view account verification status, and to
7 | * update their password.
8 | *
9 | * @signature ``
10 | * Creates the user details form.
11 | *
12 | * @param {bitballs/model/session} session The session object. If a user is
13 | * currently logged in, contains data about that user.
14 | *
15 | *
16 | * @body
17 | *
18 | * To create a `` element, pass the [bitballs/model/session] like:
19 | *
20 | * ```
21 | *
24 | * ```
25 | *
26 | * ## Example
27 | *
28 | * @demo public/components/user/details/details.html
29 | */
30 |
31 | import { Component, DefineMap, route } from "can";
32 | import User from "bitballs/models/user";
33 | import Session from "bitballs/models/session";
34 | import "bootstrap/dist/css/bootstrap.css";
35 | import view from "./details.stache!";
36 |
37 | /**
38 | * @constructor bitballs/components/user/details.ViewModel ViewModel
39 | * @parent bitballs/components/user/details
40 | *
41 | * @description A `` component's viewModel.
42 | */
43 |
44 | export const ViewModel = DefineMap.extend({
45 | /**
46 | * @property {bitballs/models/session|null}
47 | *
48 | * If a user is logged in, the session data, including
49 | * data about the currently logged in user.
50 | *
51 | * @signature `bitballs/models/session`
52 | *
53 | * A session instance, which includes data about the logged in user like:
54 | *
55 | * {
56 | * user: {
57 | * email: "tomrobbins@tommyrotten.net",
58 | * id: 4,
59 | * verified: false,
60 | * isAdmin: false
61 | * }
62 | * }
63 | *
64 | * @signature `null`
65 | *
66 | * If the user is not currently logged in, `null`.
67 | */
68 | session: {
69 | default: null
70 | },
71 | /**
72 | * @property {Promise} bitballs/components/user/details.savePromise savePromise
73 | * @parent bitballs/components/users/details.properties
74 | *
75 | * The promise that resolves when the user is saved
76 | */
77 | savePromise: 'any',
78 | /**
79 | * @property {can-define}
80 | *
81 | * Provides a user instance. If a session is active, this
82 | * syncs the user with `session.user`. Otherwise, a user instance
83 | * is created since this property is used to bind with the user details form.
84 | *
85 | */
86 | user: {
87 | Default: User,
88 | get: function(val) {
89 | if (this.session) {
90 | return this.session.user;
91 | }
92 | return val;
93 | }
94 | },
95 | /**
96 | * @property {String}
97 | *
98 | * The status of the user. One of the following:
99 | *
100 | * - "new": user has not been created
101 | * - "pending": user has been created, but has not verified their email address
102 | * - "verified": user has verified their email address
103 | *
104 | * With a new user, the component shows a registration form.
105 | * With a pending user, the component shows the email address.
106 | * With a verified user, the component shows a form allowing the user to change their password.
107 | */
108 | get userStatus() {
109 | if (this.user.isNew()) {
110 | return "new";
111 | }
112 | if (!this.user.verified) {
113 | return "pending";
114 | }
115 | return "verified";
116 | },
117 | /**
118 | * @property {Boolean}
119 | *
120 | * Whether the user has not been created yet.
121 | */
122 | isNewUser: {
123 | get: function get() {
124 | return this.user.isNew();
125 | }
126 | },
127 | /**
128 | * @function saveUser
129 | *
130 | * If the user is being created, creates a new user and when successful:
131 | * - Creates a new session
132 | * - Logs the new user in
133 | * - Changes the page route from "register" to "account"
134 | *
135 | * If the user's password is being updated, updates the password and
136 | * when successful, clears the form.
137 | *
138 | * @return {Promise<>} A promise that allows the component to display errors, if any.
139 | *
140 | */
141 | saveUser: function(ev) {
142 | if(ev) { ev.preventDefault(); }
143 | var self = this,
144 | isNew = this.user.isNew(),
145 | promise = this.user.save().then(function(user) {
146 | user.password = "";
147 | user.verificationHash = "";
148 | user.newPassword = null;
149 |
150 | if (!self.session) {
151 | self.session = new Session({
152 | user: user
153 | });
154 | } else {
155 | self.session.user = user;
156 | }
157 | if (isNew) {
158 | route.page = "account";
159 | }
160 | });
161 | this.savePromise = promise;
162 | return promise;
163 | },
164 | /**
165 | * @function deleteUser
166 | *
167 | * Confirms that the user would like to delete his or her account, then
168 | * destroys the user and when successful:
169 | * - Logs the user out, destroying the current session
170 | * - Changes the page route from "account" to "register"
171 | */
172 | deleteUser: function() {
173 | var self = this;
174 | if (confirm('Are you sure you want to delete your account?')) {
175 | this.user.destroy(function() {
176 | self.session.destroy();
177 | self.session = null;
178 | route.page = "register";
179 | });
180 | }
181 | }
182 | });
183 |
184 | export const UserDetails = Component.extend({
185 | tag: "user-details",
186 | view,
187 | ViewModel
188 | });
189 |
190 | export { UserDetails as Component };
191 |
--------------------------------------------------------------------------------
/public/components/user/details/details.stache:
--------------------------------------------------------------------------------
1 |
2 | {{# switch(userStatus) }}
3 | {{# case("new") }}
4 | Register New User
5 | {{/ case }}
6 |
7 | {{# case("pending") }}
8 | Verify User
9 | {{/ case }}
10 |
11 | {{# default }}
12 | Update User
13 | {{/ default }}
14 | {{/ switch }}
15 |
16 |
87 |
--------------------------------------------------------------------------------
/public/components/user/details/details_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from 'steal-qunit';
2 | import { ViewModel } from 'bitballs/components/user/details/';
3 | import 'bitballs/models/fixtures/users';
4 |
5 | QUnit.module('components/user/', {
6 | beforeEach: function() {
7 | }
8 | });
9 |
10 | QUnit.test('create new user', function(assert) {
11 | assert.expect(5);
12 | var done = assert.async();
13 |
14 | var vm = new ViewModel();
15 |
16 | vm.user.set({
17 | email: 'test@bitovi.com',
18 | password: '123'
19 | });
20 |
21 | // session is not created before user is saved:
22 | assert.ok(vm.user.isNew(), 'User should be new.');
23 |
24 | assert.equal(vm.session, null, 'Session should not exist before user gets created.');
25 |
26 | vm.saveUser().then(function(){
27 | assert.equal(vm.session.user.email, 'test@bitovi.com', 'Session email should be set after user gets created.');
28 | assert.notOk(vm.user.isNew(), 'User should not be new any more.');
29 | assert.equal(vm.user.password, '', 'User\'s password property should be cleared after user gets created/updated.');
30 |
31 | done();
32 | }, function() {
33 | done();
34 | });
35 | });
36 |
37 | QUnit.test('saveUser without password fails', function(assert) {
38 | var done = assert.async();
39 |
40 | var vm = new ViewModel();
41 |
42 |
43 | vm.user.email = 'test@bitovi.com';
44 | assert.expect(2);
45 |
46 | vm.saveUser();
47 | vm.savePromise.then(function(resp, type){
48 | done();
49 | }, function(resp) {
50 | assert.equal(resp.statusText, 'error', 'fail creation without password');
51 | assert.equal(resp.status, 400, 'rejected');
52 | done();
53 | });
54 |
55 | });
56 |
--------------------------------------------------------------------------------
/public/components/user/details/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/components/user/list/list.html:
--------------------------------------------------------------------------------
1 |
12 |
60 |
--------------------------------------------------------------------------------
/public/components/user/list/list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {Module} bitballs/components/user/list
3 | * @parent bitballs.components
4 | *
5 | * @description Provides a custom element that allows an admin user
6 | * to view the list of registered users, see whether they have
7 | * verified their email addresses, and change their admin status.
8 | *
9 | * @signature ``
10 | * Creates the user list.
11 | *
12 | * @param {bitballs/model/session} session The session object. Contains information
13 | * about the logged in user. If the logged in user is not an administrator, they
14 | * will not be able to view the user list. This also allows the component to prevent
15 | * the logged in user from removing themseves as an administrator.
16 | *
17 | * @body
18 | *
19 | * To create a `` element, pass the [bitballs/model/session] like:
20 | *
21 | * ```
22 | *
25 | * ```
26 | *
27 | * ## Example
28 | *
29 | * @demo public/components/user/list/list.html
30 | */
31 | import { Component, DefineMap } from "can";
32 | import './list.less';
33 | import view from './list.stache';
34 | import User from "bitballs/models/user";
35 | import Session from "bitballs/models/session";
36 |
37 | /**
38 | * @constructor bitballs/components/user/list.ViewModel ViewModel
39 | * @parent bitballs/components/user/list
40 | *
41 | * @description A `` component's ViewModel.
42 | */
43 |
44 | export const ViewModel = DefineMap.extend({
45 | /**
46 | * @property {bitballs/models/session} session
47 | * The session object if a user is logged in. The user must be an admin to view the user list.
48 | */
49 | session: Session,
50 | /**
51 | * @property {can-list}
52 | *
53 | * Provides list of users, like:
54 | *
55 | * {data: [{
56 | * "id": Int,
57 | * "email": String,
58 | * "isAdmin": Boolean,
59 | * "verified": Boolean
60 | * }, ...]}
61 | *
62 | */
63 | users: {
64 | get: function(list) {
65 | if (list) {
66 | return list;
67 | }
68 | return User.getList({});
69 | }
70 | },
71 | /**
72 | * @function
73 | *
74 | * Sets the user's admin status.
75 | *
76 | * @param {bitballs/models/user} user The user object that will be set or unset as an admin.
77 | * @param {Boolean} isAdmin Whether the user should be set as an admin.
78 | *
79 | * @return {Promise
3 | @signature ``
4 |
5 | @body
6 |
7 | ## Users
8 |
--------------------------------------------------------------------------------
/public/components/user/list/list.stache:
--------------------------------------------------------------------------------
1 | {{# if(session.isAdmin() ) }}
2 |
43 | {{/ if }}
44 |
--------------------------------------------------------------------------------
/public/components/user/list/list_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from 'steal-qunit';
2 |
3 | // ViewModel unit tests
4 | QUnit.module('bitballs/components/user/list');
5 |
6 | QUnit.test('Has message', function(){
7 | QUnit.ok(true, 'Has a test');
8 | });
9 |
--------------------------------------------------------------------------------
/public/components/user/list/test.html:
--------------------------------------------------------------------------------
1 | bitballs/components/user/list
2 |
3 |
--------------------------------------------------------------------------------
/public/components/user/test.js:
--------------------------------------------------------------------------------
1 | import './details/details_test';
2 | import './list/list_test';
--------------------------------------------------------------------------------
/public/dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/img/bitballs-logo-02.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/index.stache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{# if(pageComponent.isResolved) }}
16 | {{pageComponent.value}}
17 | {{ else }}
18 | Loading...
19 | {{/ if }}
20 |
21 |
22 | {{# is(env.NODE_ENV, "production") }}
23 |
24 | {{ else }}
25 |
27 | {{/ is }}
28 |
29 |
30 |
--------------------------------------------------------------------------------
/public/inserted-removed.js:
--------------------------------------------------------------------------------
1 | import domEvents from "can-dom-events";
2 | import domMutateDomEvents from "can-dom-mutate/dom-events";
3 |
4 | domEvents.addEvent(domMutateDomEvents.inserted);
5 | domEvents.addEvent(domMutateDomEvents.removed);
6 |
--------------------------------------------------------------------------------
/public/is-dev.js:
--------------------------------------------------------------------------------
1 | import steal from "@steal";
2 |
3 | // The slim loader doesn't include a isEnv, so that means it's prod.
4 | export default !(!steal.isEnv || steal.isEnv("production"));
5 |
--------------------------------------------------------------------------------
/public/models/bookshelf-service.js:
--------------------------------------------------------------------------------
1 | import { key } from "can";
2 |
3 | const bookshelfService = {
4 | toQuery(params) {
5 | return key.transform(params, {
6 | where: "filter",
7 | orderBy: "sort"
8 | });
9 | },
10 | toParams(query){
11 | return key.transform(query, {
12 | filter: "where",
13 | sort: "orderBy"
14 | });
15 | }
16 | };
17 |
18 | export default bookshelfService;
19 |
--------------------------------------------------------------------------------
/public/models/fixtures/fixtures.js:
--------------------------------------------------------------------------------
1 | import "./games";
2 | import "./players";
3 | import "./tournaments";
4 | import "./users";
5 | import "./stats";
6 |
--------------------------------------------------------------------------------
/public/models/fixtures/games.js:
--------------------------------------------------------------------------------
1 | import { fixture } from "can";
2 |
3 | export const games = {
4 | id: 1,
5 | videoUrl: "AEUULIs_UWE",
6 | homeTeamId: 1,
7 | round: "Round 1",
8 | court: "1",
9 | tournament: {
10 | id: 1,
11 | date: "2012-01-01"
12 | },
13 | finalScore: {
14 | home: 22,
15 | away: 20
16 | },
17 | currentScore: {
18 | home: 0,
19 | away: 0
20 | },
21 | homeTeam: {
22 | id: 1,
23 | player1Id:1,
24 | name: "Solid as A Rock",
25 | color: "I Blue Myself",
26 | player1:{
27 | id: 1,
28 | name: "George Bluth",
29 | weight: 180,
30 | height: 60,
31 | birthday: "14/11/1960",
32 | profile: "This is a player",
33 | startRank: ""
34 | },
35 | player2Id:2,
36 | player2:{
37 | id: 2,
38 | name: "Micheal Bluth",
39 | weight: 180,
40 | height: 60,
41 | birthday: "14/11/1960",
42 | profile: "This is a player",
43 | startRank: ""
44 | },
45 | player3Id:3,
46 | player3:{
47 | id: 3,
48 | name: "Lucille Bluth",
49 | weight: 180,
50 | height: 60,
51 | birthday: "14/11/1960",
52 | profile: "This is a player",
53 | startRank: ""
54 | },
55 | player4Id:4,
56 | player4:{
57 | id: 4,
58 | name: "Oscar Bluth",
59 | weight: 180,
60 | height: 60,
61 | birthday: "14/11/1960",
62 | profile: "This is a player",
63 | startRank: ""
64 | }
65 | },
66 | awayTeamId: 2,
67 | awayTeam: {
68 | id: 2,
69 | player1Id:5,
70 | name: "Bob Loblaw Balls, Y'all",
71 | color: "Tobias's Favorite Shade of Pink",
72 | player1:{
73 | id: 5,
74 | name: "Lucille Two",
75 | weight: 180,
76 | height: 60,
77 | birthday: "14/11/1960",
78 | profile: "This is a player",
79 | startRank: ""
80 | },
81 | player2Id:6,
82 | player2:{
83 | id: 6,
84 | name: "Anne",
85 | weight: 180,
86 | height: 60,
87 | birthday: "14/11/1960",
88 | profile: "This is a player",
89 | startRank: ""
90 | },
91 | player3Id:7,
92 | player3:{
93 | id: 7,
94 | name: "Bob Loblaw",
95 | weight: 180,
96 | height: 60,
97 | birthday: "14/11/1960",
98 | profile: "This is a player",
99 | startRank: ""
100 | },
101 | player4Id:8,
102 | player4:{
103 | id: 8,
104 | name: "Steve Holt",
105 | weight: 180,
106 | height: 60,
107 | birthday: "14/11/1960",
108 | profile: "This is a player",
109 | startRank: ""
110 | }
111 | },
112 | stats: [{
113 | id: 1,
114 | type: "1P",
115 | playerId: 4,
116 | time: 20
117 | },
118 | {
119 | id: 2,
120 | type: "2P",
121 | playerId: 4,
122 | time: 40
123 | },
124 | {
125 | id: 3,
126 | type: "1P",
127 | playerId: 5,
128 | time: 60
129 | },
130 | {
131 | id: 4,
132 | type: "1P",
133 | playerId: 6,
134 | time: 80
135 | },
136 | {
137 | id: 5,
138 | type: "1P",
139 | playerId: 7,
140 | time: 100
141 | },
142 | {
143 | id: 6,
144 | type: "2P",
145 | playerId: 8,
146 | time: 120
147 | }]
148 | };
149 |
150 | export const defineFixtures = function () {
151 |
152 | fixture('/services/games', function () {
153 | return {
154 | data: [games]
155 | };
156 | });
157 |
158 | fixture("/services/games/{id}", function(request, response) {
159 | if (request.data.id === "1" || request.data.id === 1) {
160 | response(games);
161 | }
162 | });
163 |
164 | fixture('GET /services/stats', function () {
165 | return { data: games.stats };
166 | });
167 |
168 | fixture('DELETE /services/stats/{id}', function () {
169 | return {};
170 | });
171 | };
172 |
173 | defineFixtures();
174 |
175 | export default defineFixtures;
176 |
--------------------------------------------------------------------------------
/public/models/fixtures/players.js:
--------------------------------------------------------------------------------
1 | import { fixture } from 'can';
2 |
3 | export const players = {
4 | data: [{
5 | id: 1,
6 | name: 'Test Player',
7 | weight: 200,
8 | height: 71,
9 | birthday: '1980-01-01',
10 | profile:null,
11 | startRank:null
12 | }]
13 | };
14 |
15 | export const defineFixtures = function() {
16 |
17 | fixture('GET /services/players/{id}', function(req) {
18 | var data;
19 | players.data.forEach(function(player){
20 | if (player.id === parseInt(req.data.id, 10)) {
21 | data = player;
22 | return true;
23 | }
24 | });
25 | return data;
26 | });
27 |
28 | fixture('GET /services/players', function(req) {
29 | return players;
30 | });
31 |
32 | fixture('POST /services/players', function(request, response){
33 | if(!request.data.name){
34 | response(400, '{type: "Bad Request", message: "Can not create a player without a name"}');
35 | }else{
36 | response({
37 | "id":1
38 | });
39 | }
40 | });
41 |
42 | fixture('PUT /services/players/{id}', function(req) {
43 | req.data.id = parseInt(req.data.id, 10);
44 | return req.data;
45 | });
46 | };
47 |
48 | defineFixtures();
49 |
50 | export default defineFixtures;
51 |
--------------------------------------------------------------------------------
/public/models/fixtures/stats.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {"id":1,"gameId":1,"playerId":1,"type":"1P","time":15,"value":null},
3 | {"id":2,"gameId":1,"playerId":7,"type":"1PA","time":32,"value":null},
4 | {"id":3,"gameId":1,"playerId":3,"type":"1PA","time":43,"value":null},
5 | {"id":4,"gameId":1,"playerId":7,"type":"1PA","time":49,"value":null},
6 | {"id":5,"gameId":1,"playerId":2,"type":"1PA","time":54,"value":null},
7 | {"id":6,"gameId":1,"playerId":1,"type":"1PA","time":84,"value":null},
8 | {"id":7,"gameId":1,"playerId":3,"type":"To","time":97,"value":null},
9 | {"id":8,"gameId":1,"playerId":6,"type":"1P","time":106,"value":null},
10 | {"id":9,"gameId":1,"playerId":1,"type":"2PA","time":147,"value":null},
11 | {"id":10,"gameId":1,"playerId":6,"type":"1PA","time":157,"value":null},
12 | {"id":11,"gameId":1,"playerId":1,"type":"2PA","time":164,"value":null},
13 | {"id":12,"gameId":1,"playerId":7,"type":"1P","time":172,"value":null},
14 | {"id":13,"gameId":1,"playerId":2,"type":"1PA","time":203,"value":null},
15 | {"id":14,"gameId":1,"playerId":6,"type":"1PA","time":216,"value":null},
16 | {"id":15,"gameId":1,"playerId":1,"type":"1PA","time":226,"value":null},
17 | {"id":16,"gameId":1,"playerId":6,"type":"1PA","time":230,"value":null},
18 | {"id":17,"gameId":1,"playerId":1,"type":"2PA","time":240,"value":null},
19 | {"id":18,"gameId":1,"playerId":8,"type":"2PA","time":260,"value":null},
20 | {"id":19,"gameId":1,"playerId":6,"type":"1P","time":282,"value":null},
21 | {"id":20,"gameId":1,"playerId":5,"type":"1P","time":307,"value":null},
22 | {"id":21,"gameId":1,"playerId":5,"type":"1PA","time":335,"value":null},
23 | {"id":22,"gameId":1,"playerId":2,"type":"1PA","time":355,"value":null},
24 | {"id":23,"gameId":1,"playerId":6,"type":"1PA","time":365,"value":null},
25 | {"id":24,"gameId":1,"playerId":5,"type":"Stl","time":392,"value":null},
26 | {"id":25,"gameId":1,"playerId":5,"type":"1P","time":407,"value":null},
27 | {"id":26,"gameId":1,"playerId":6,"type":"1P","time":432,"value":null},
28 | {"id":27,"gameId":1,"playerId":3,"type":"2P","time":449,"value":null},
29 | {"id":28,"gameId":1,"playerId":7,"type":"1PA","time":469,"value":null},
30 | {"id":29,"gameId":1,"playerId":1,"type":"1PA","time":481,"value":null},
31 | {"id":30,"gameId":1,"playerId":6,"type":"1P","time":495,"value":null},
32 | {"id":31,"gameId":1,"playerId":5,"type":"1PA","time":510,"value":null},
33 | {"id":32,"gameId":1,"playerId":5,"type":"1P","time":513,"value":null},
34 | {"id":33,"gameId":1,"playerId":6,"type":"1PA","time":621,"value":null},
35 | {"id":34,"gameId":1,"playerId":1,"type":"2P","time":629,"value":null},
36 | {"id":35,"gameId":1,"playerId":6,"type":"1P","time":652,"value":null},
37 | {"id":36,"gameId":1,"playerId":1,"type":"2P","time":671,"value":null},
38 | {"id":37,"gameId":1,"playerId":3,"type":"1PA","time":723,"value":null},
39 | {"id":38,"gameId":1,"playerId":7,"type":"1P","time":740,"value":null},
40 | {"id":39,"gameId":1,"playerId":5,"type":"2P","time":766,"value":null},
41 | {"id":40,"gameId":1,"playerId":6,"type":"2P","time":784,"value":null},
42 | {"id":41,"gameId":1,"playerId":6,"type":null,"time":814,"value":null},
43 | {"id":42,"gameId":1,"playerId":1,"type":"2PA","time":821,"value":null},
44 | {"id":43,"gameId":1,"playerId":7,"type":"1PA","time":854,"value":null},
45 | {"id":44,"gameId":1,"playerId":5,"type":"1PA","time":860,"value":null},
46 | {"id":45,"gameId":1,"playerId":7,"type":"1PA","time":877,"value":null},
47 | {"id":46,"gameId":1,"playerId":5,"type":"2PA","time":883,"value":null},
48 | {"id":47,"gameId":1,"playerId":7,"type":"1PA","time":899,"value":null},
49 | {"id":48,"gameId":1,"playerId":3,"type":"1PA","time":908,"value":null},
50 | {"id":49,"gameId":1,"playerId":6,"type":"2PA","time":912,"value":null},
51 | {"id":50,"gameId":1,"playerId":6,"type":"1P","time":918,"value":null},
52 | {"id":51,"gameId":1,"playerId":6,"type":"1P","time":958,"value":null},
53 | {"id":52,"gameId":1,"playerId":1,"type":"1PA","time":1018,"value":null},
54 | {"id":53,"gameId":1,"playerId":3,"type":"1P","time":1066,"value":null},
55 | {"id":54,"gameId":1,"playerId":6,"type":"2PA","time":1131,"value":null},
56 | {"id":55,"gameId":1,"playerId":6,"type":"1P","time":1149,"value":null},
57 | {"id":56,"gameId":1,"playerId":5,"type":"1PA","time":1162,"value":null},
58 | {"id":57,"gameId":1,"playerId":6,"type":"1PA","time":1169,"value":null},
59 | {"id":58,"gameId":1,"playerId":3,"type":"1PA","time":1175,"value":null},
60 | {"id":59,"gameId":1,"playerId":1,"type":"2PA","time":1186,"value":null},
61 | {"id":60,"gameId":1,"playerId":1,"type":"2PA","time":1202,"value":null},
62 | {"id":61,"gameId":1,"playerId":6,"type":"1PA","time":1207,"value":null},
63 | {"id":62,"gameId":1,"playerId":5,"type":"1P","time":1220,"value":null},
64 | {"id":63,"gameId":1,"playerId":6,"type":"1P","time":1246,"value":null},
65 | {"id":64,"gameId":5,"playerId":8,"type":"1P","time":189,"value":null},
66 | {"id":65,"gameId":9,"playerId":40,"type":"2PA","time":9,"value":null},
67 | {"id":66,"gameId":9,"playerId":40,"type":"1P","time":12,"value":null},
68 | {"id":67,"gameId":9,"playerId":40,"type":"ORB","time":10,"value":null},
69 | {"id":68,"gameId":9,"playerId":42,"type":"1PA","time":13,"value":null},
70 | {"id":69,"gameId":9,"playerId":35,"type":"2P","time":229,"value":null},
71 | {"id":71,"gameId":9,"playerId":19,"type":"1PA","time":24,"value":null},
72 | {"id":72,"gameId":9,"playerId":35,"type":"ORB","time":25,"value":null},
73 | {"id":73,"gameId":9,"playerId":35,"type":"1P","time":27,"value":null},
74 | {"id":75,"gameId":9,"playerId":41,"type":"2PA","time":53,"value":null},
75 | {"id":77,"gameId":9,"playerId":35,"type":"2P","time":79,"value":null},
76 | {"id":78,"gameId":9,"playerId":41,"type":"2P","time":90,"value":null},
77 | {"id":79,"gameId":9,"playerId":23,"type":"2P","time":109,"value":null}
78 | ];
--------------------------------------------------------------------------------
/public/models/fixtures/teams.js:
--------------------------------------------------------------------------------
1 | import { fixture } from "can";
2 |
3 | export const teams = {
4 | data: [{
5 | id: 1,
6 | name: 'Three Dog Night',
7 | color: 'Red',
8 | player1Id: 1
9 | }]
10 | };
11 |
12 | export const defineFixtures = function () {
13 | fixture('services/teams', function () {
14 | return teams;
15 | });
16 |
17 | fixture({method: "DELETE", url: 'services/teams/{id}'}, function() {
18 | return [];
19 | });
20 | };
21 |
22 | defineFixtures();
23 |
24 | export default defineFixtures;
25 |
--------------------------------------------------------------------------------
/public/models/fixtures/tournaments.js:
--------------------------------------------------------------------------------
1 | import { assign, fixture } from "can";
2 | import $ from 'jquery';
3 |
4 | export const tournaments = {
5 | data: [
6 | {
7 | date: new Date("Fri Sep 04 2015 07:42:58 GMT-0500 (CDT)"),
8 | id: 2,
9 | tournamentId: 1,
10 | name: "EBaller Virus",
11 | color: "Yellow",
12 | player1Id: 1,
13 | player2Id: 2,
14 | player3Id: 3,
15 | player4Id: 5
16 | }
17 | ]
18 | };
19 |
20 | export const defineFixtures = function () {
21 | fixture('POST /services/tournaments', function (req) {
22 | var data = assign({}, req.data);
23 | assign(data, {
24 | date: new Date(req.data.date),
25 | id: tournaments.data[tournaments.data.length - 1].id + 1
26 | });
27 |
28 | tournaments.data.push(data);
29 | return data;
30 | });
31 |
32 | fixture('POST /services/tournaments', function(req, response){
33 | if(!req.data.date){
34 | response(400, '{type: "Bad Request", message: "Can not create a tournament without a date"}');
35 | } else {
36 | response({
37 | id: 3
38 | });
39 | }
40 | });
41 |
42 | fixture('GET /services/tournaments', function (req) {
43 | return tournaments;
44 | });
45 |
46 | fixture('GET /services/tournaments/{id}', function (req) {
47 | var data;
48 | $.each(tournaments.data, function (i, tourney) {
49 | if (tourney.id === parseInt(req.data.id, 10)) {
50 | data = tourney;
51 | return false;
52 | }
53 | });
54 | return data;
55 | });
56 | };
57 |
58 | defineFixtures();
59 |
60 | export default defineFixtures;
61 |
--------------------------------------------------------------------------------
/public/models/fixtures/users.js:
--------------------------------------------------------------------------------
1 | import { fixture } from "can";
2 |
3 | fixture("POST /services/users", function(request, response){
4 | console.log('[fixture] request', request);
5 |
6 | if(!request.data.password){
7 | response(400, '{type: "Bad Request", message: "Can not create a user without a password"}');
8 | }else{
9 | response({
10 | id: 123,
11 | email: request.data.email
12 | });
13 | }
14 | });
15 |
16 | fixture("PUT /services/users/{id}", function(request, response){
17 | if(!request.data.password){
18 | response(400, '{type: "Bad Request", message: "Can not create a user without a password"}');
19 | }else{
20 | response({
21 | id: request.data.id,
22 | email: request.data.email
23 | });
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/public/models/game_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from "steal-qunit";
2 | import Game from './game';
3 |
4 | QUnit.module('bitballs/models/game', {
5 | setup: function () {
6 | this.game = new Game();
7 | }
8 | });
9 |
10 | QUnit.test('Video url can be a YouTube url or key', function () {
11 | var game = this.game;
12 | var videoKey = '0zM3nApSvMg';
13 |
14 | var sampleUrls = [
15 | '0zM3nApSvMg',
16 | 'http://www.youtube.com/v/0zM3nApSvMg?fs=1&hl=en_US&rel=0',
17 | 'http://www.youtube.com/embed/0zM3nApSvMg?rel=0',
18 | 'http://www.youtube.com/watch?v=0zM3nApSvMg&feature=feedrec_grec_index',
19 | 'http://www.youtube.com/watch?v=0zM3nApSvMg',
20 | 'http://youtu.be/0zM3nApSvMg',
21 | 'http://www.youtube.com/watch?v=0zM3nApSvMg#t=0m10s',
22 | 'http://www.youtube.com/user/IngridMichaelsonVEVO#p/a/u/1/0zM3nApSvMg',
23 | 'http://youtu.be/0zM3nApSvMg',
24 | 'http://www.youtube.com/embed/0zM3nApSvMg',
25 | 'http://www.youtube.com/v/0zM3nApSvMg',
26 | 'http://www.youtube.com/e/0zM3nApSvMg',
27 | 'http://www.youtube.com/watch?v=0zM3nApSvMg',
28 | 'http://www.youtube.com/?v=0zM3nApSvMg',
29 | 'http://www.youtube.com/watch?feature=player_embedded&v=0zM3nApSvMg',
30 | 'http://www.youtube.com/?feature=player_embedded&v=0zM3nApSvMg',
31 | 'http://www.youtube.com/user/IngridMichaelsonVEVO#p/u/11/0zM3nApSvMg',
32 | 'http://www.youtube-nocookie.com/v/0zM3nApSvMg?version=3&hl=en_US&rel=0'
33 | ];
34 | sampleUrls.forEach(function(url){
35 | game.attr('videoUrl', url);
36 | QUnit.equal(game.attr('videoUrl'), videoKey, 'Video key was extracted from input');
37 | });
38 | });
39 |
40 | QUnit.test('Rounds are not available if all their courts are assigned games', function () {
41 | var gameList = new Game.List();
42 |
43 | Game.courtNames.forEach(function (courtName) {
44 | gameList.push({
45 | round: Game.roundNames[0],
46 | court: courtName
47 | });
48 | });
49 |
50 | QUnit.deepEqual(gameList.getAvailableRounds(), Game.roundNames.slice(1),
51 | 'The first round is not available');
52 | });
53 |
54 | QUnit.test('Courts are not available if they are assigned games', function () {
55 | var gameList = new Game.List([{
56 | round: Game.roundNames[0],
57 | court: Game.courtNames[0]
58 | }]);
59 |
60 | QUnit.deepEqual(gameList.getAvailableCourts(Game.roundNames[0]), Game.courtNames.slice(1),
61 | 'The first court is not available');
62 | });
63 |
64 |
--------------------------------------------------------------------------------
/public/models/player.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {can-map} bitballs/models/player Player
3 | * @parent bitballs.clientModels
4 | *
5 | * @group bitballs/models/player.properties 0 properties
6 | */
7 | import { superModel, QueryLogic, DefineMap, DefineList, defineBackup } from "can";
8 | import moment from "moment";
9 | import bookshelfService from "./bookshelf-service";
10 |
11 | var Player = DefineMap.extend('Player', {
12 | /**
13 | * @property {Number} bitballs/models/player.properties.id id
14 | * @parent bitballs/models/player.properties
15 | *
16 | * A unique identifier.
17 | **/
18 | id: {type: 'number', identity: true},
19 |
20 | /**
21 | * @property {String} bitballs/models/player.properties.birthday birthday
22 | * @parent bitballs/models/player.properties
23 | *
24 | * The player's date of birth. Formatted as `YYYY-MM-DD`.
25 | **/
26 | birthday: 'any',
27 | /**
28 | * @property {String} bitballs/models/player.properties.name name
29 | * @parent bitballs/models/player.properties
30 | *
31 | * The name of the player.
32 | **/
33 | name: 'string',
34 | /**
35 | * @property {Number} bitballs/models/player.properties.weight weight
36 | * @parent bitballs/models/player.properties
37 | *
38 | * The weight of a player in pounds.
39 | **/
40 | weight: 'number',
41 | /**
42 | * @property {Number} bitballs/models/player.properties.height height
43 | * @parent bitballs/models/player.properties
44 | *
45 | * The height of a player in inches.
46 | **/
47 | height: 'number',
48 |
49 | // flag set by the api when a player is destroyed
50 | _destroyed: 'boolean',
51 |
52 | profile: 'any',
53 |
54 | startRank: 'any',
55 |
56 | /**
57 | * @function
58 | *
59 | * Backs up the model's properties on instantiation.
60 | **/
61 | init: function () {
62 | this.backup();
63 | },
64 | /**
65 | * @property {Date|null} bitballs/models/player.properties.jsBirthday jsBirthday
66 | * @parent bitballs/models/player.properties
67 | *
68 | * The [bitballs/models/player.properties.birthday birthday] property
69 | * represented as a JavaScript object.
70 | **/
71 | get jsBirthday() {
72 | var date = this.birthday;
73 | return date ? new Date(date) : null;
74 | },
75 | /**
76 | * @property {String} bitballs/models/player.properties.birthDate birthDate
77 | * @parent bitballs/models/player.properties
78 | *
79 | * The [bitballs/models/player.properties.birthday birthday] property
80 | * formatted as `YYYY-MM-DD`.
81 | **/
82 | get birthDate() {
83 | var date = this.birthday;
84 | return date ? moment(date).format('YYYY-MM-DD') : "";
85 | },
86 | set birthDate(value) {
87 | this.birthday = value;
88 | },
89 | /**
90 | * @property {Number} bitballs/models/player.properties.age age
91 | * @parent bitballs/models/player.properties
92 | *
93 | * The number of full years since the date of the
94 | * [bitballs/models/player.properties.jsBirthday jsBirthday] property.
95 | **/
96 | get age() {
97 | var birthDate = this.jsBirthday;
98 | if(birthDate) {
99 | var today = new Date();
100 | var age = today.getFullYear() - birthDate.getFullYear();
101 | var m = today.getMonth() - birthDate.getMonth();
102 | if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
103 | age--;
104 | }
105 | return age;
106 | }
107 | }
108 | });
109 | defineBackup(Player);
110 |
111 | /**
112 | * @constructor {can-list} bitballs/models/player.static.List List
113 | * @parent bitballs/models/player.static
114 | */
115 | Player.List = DefineList.extend('PlayerList',
116 | /** @prototype **/
117 | {
118 | "#": Player,
119 | /**
120 | * @property {Object}
121 | *
122 | * A map of player ids to [bitballs/models/player] models.
123 | **/
124 | get idMap() {
125 | var map = {};
126 | this.forEach(function(player){
127 | map[player.id] = player;
128 | });
129 |
130 | return map;
131 | },
132 |
133 | /**
134 | * @function
135 | *
136 | * Returns a Player in the list of players given its id.
137 | *
138 | * @param {Number} id
139 | * @return {bitballs/models/player|undefined} The player if it exists.
140 | */
141 | getById: function(id){
142 | return this.idMap[id];
143 | }
144 | });
145 |
146 | Player.connection = superModel({
147 | Map: Player,
148 | List: Player.List,
149 | url: {
150 | resource: "/services/players",
151 | contentType: 'application/x-www-form-urlencoded'
152 | },
153 | name: "player",
154 | queryLogic: new QueryLogic(Player, bookshelfService),
155 | updateInstanceWithAssignDeep: true
156 | });
157 |
158 |
159 |
160 |
161 | export default Player;
162 |
--------------------------------------------------------------------------------
/public/models/session.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {can-map} bitballs/models/session Session
3 | * @parent bitballs.clientModels
4 | *
5 | * @group bitballs/models/session.properties 0 properties
6 | */
7 |
8 | import {
9 | restModel,
10 | QueryLogic,
11 | DefineMap,
12 | DefineList
13 | } from "can";
14 | import bookshelfService from "./bookshelf-service";
15 | import $ from "jquery";
16 | import User from "./user";
17 |
18 |
19 | var Session = DefineMap.extend('Session', {
20 | /**
21 | * @property {bitballs/models/user} bitballs/models/session.properties.user user
22 | * @parent bitballs/models/session.properties
23 | *
24 | * The [bitballs/models/user] model this session represents.
25 | **/
26 | user: User,
27 | /**
28 | * @function
29 | *
30 | * Identifies whether or not the [bitballs/models/session.properties.user]
31 | * property is an administrator.
32 | *
33 | * @return {Boolean}
34 | **/
35 | isAdmin: function(){
36 | return this.user && this.user.isAdmin;
37 | }
38 | });
39 |
40 | /**
41 | * @constructor {can-list} bitballs/models/session.static.List List
42 | * @parent bitballs/models/session.static
43 | */
44 | Session.List = DefineList.extend('SessionList', {"#": Session});
45 |
46 |
47 | Session.connection = restModel({
48 | ajax: $.ajax,
49 | Map: Session,
50 | List: Session.List,
51 | //name: "session",
52 | url: {
53 | getData: "/services/session",
54 | createData: "/services/session",
55 | destroyData: "/services/session",
56 | contentType: "application/x-www-form-urlencoded"
57 | },
58 | queryLogic: new QueryLogic(Session, bookshelfService),
59 | updateInstanceWithAssignDeep: true
60 | });
61 |
62 | export default Session;
63 |
--------------------------------------------------------------------------------
/public/models/stat.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {can-map} bitballs/models/stat Stat
3 | * @parent bitballs.clientModels
4 | *
5 | * @group bitballs/models/stat.properties 0 properties
6 | *
7 | * A [can.Map](https://canjs.com/docs/can.Map.html) that's connected to the [services/stats] with
8 | * all of [can-connect/can/super-map](https://connect.canjs.com/doc/can-connect%7Ccan%7Csuper-map.html)'s
9 | * behaviors.
10 | *
11 | * @body
12 | *
13 | * ## Use
14 | *
15 | * Use the `Stat` model to CRUD stats on the server. Use the CRUD methods `getList`, `save`, and `destroy` added to
16 | * `Stat` by the [can-connect/can/map](https://connect.canjs.com/doc/can-connect%7Ccan%7Cmap.html) behavior.
17 | *
18 | *
19 | * ```
20 | * var Stat = require("bitballs/models/stat");
21 | * Stat.getList({where: {gameId: 5 }}).then(function(stats){ ... });
22 | * new Stat({gameId: 6, playerId: 15, type: "1P", time: 60}).save()
23 | * ```
24 | */
25 | import { DefineMap, DefineList, QueryLogic, realtimeRestModel } from "can";
26 | import bookshelfService from "./bookshelf-service";
27 | import Player from "bitballs/models/player";
28 |
29 | var Stat = DefineMap.extend('Stat',
30 | {
31 | /**
32 | * @property {Array<{name: String}>} statTypes
33 | *
34 | * Array of statType objects. Each object has a name property which
35 | * has the short name of the stat. Ex: `{name: "1P"}`.
36 | */
37 | statTypes: [
38 | { name: "1P"},
39 | { name: "1PA"},
40 | { name: "2P"},
41 | { name: "2PA"},
42 | { name: "ORB"},
43 | { name: "DRB"},
44 | { name: "Ast"},
45 | { name: "Stl"},
46 | { name: "Blk"},
47 | { name: "To"}
48 | ]
49 | },
50 | {
51 | /**
52 | * @property {Number} bitballs/models/stat.properties.id id
53 | * @parent bitballs/models/stat.properties
54 | * A unique identifier.
55 | **/
56 | id: {type: 'number', identity: true},
57 | /**
58 | * @property {bitballs/models/player} bitballs/models/stat.properties.player player
59 | * @parent bitballs/models/player.properties
60 | *
61 | * Player related to the stats
62 | */
63 | player: {
64 | Type: Player,
65 | serialize: false
66 | },
67 | /**
68 | * @property {Number} bitballs/models/stat.properties.playerId playerId
69 | * @parent bitballs/models/player.properties
70 | *
71 | * Player id of the current stats
72 | */
73 | playerId: 'number',
74 | /**
75 | * @property {Number} bitballs/models/stat.properties.gameId gameId
76 | * @parent bitballs/models/player.properties
77 | *
78 | * Game id of the current stat
79 | */
80 | gameId: 'number',
81 | /**
82 | * @property {Any} bitballs/models/stat.properties.type type
83 | * @parent bitballs/models/player.properties
84 | *
85 | * Type of the stat
86 | */
87 | type: 'any',
88 | /**
89 | * @property {Number} bitballs/models/stat.properties.time time
90 | * @parent bitballs/models/stat.properties
91 | *
92 | * The time of the stat, rounded to the nearest integer.
93 | */
94 | time: {
95 | set: function(newVal){
96 | return Math.round(newVal);
97 | }
98 | },
99 |
100 | default: 'any'
101 | });
102 |
103 |
104 | /**
105 | * @property {can-define/list} bitballs/models/stat.static.List List
106 | * @parent bitballs/models/stat.static
107 | *
108 | * Methods on a List of stats.
109 | */
110 | Stat.List = DefineList.extend('StatsList', {
111 | "#": Stat,
112 | get byPlayer() {
113 | let players = {};
114 |
115 | this.forEach((stat) => {
116 | if (!players[stat.playerId]) {
117 | players[stat.playerId] = new Stat.List();
118 | }
119 |
120 | players[stat.playerId].push(stat);
121 | });
122 |
123 | return players;
124 | },
125 | get players() {
126 | return Object.keys(this.byPlayer).map((id) => ({ id }));
127 | },
128 | get byGame() {
129 | let games = {};
130 |
131 | this.forEach((stat) => {
132 | if (!games[stat.gameId]) {
133 | games[stat.gameId] = new Stat.List();
134 | }
135 |
136 | games[stat.gameId].push(stat);
137 | });
138 |
139 | return games;
140 | },
141 | get games() {
142 | return Object.keys(this.byGame).map((id) => ({ id }));
143 | },
144 | get aggregated() {
145 | let aggregated = {};
146 |
147 | this.forEach(({ type }) => {
148 | if (!aggregated[type]) {
149 | aggregated[type] = 0;
150 | }
151 |
152 | aggregated[type]++;
153 | });
154 |
155 | let aggregatedStats = [
156 | ...Stat.statTypes.map(({ name }) => ({
157 | name,
158 | default: (aggregated[name] || 0).toFixed(0),
159 | })),
160 | {
161 | name: 'TP',
162 | default: (function() {
163 | let onePointers = aggregated['1P'] || 0;
164 | let twoPointers = aggregated['2P'] || 0;
165 |
166 | return (onePointers + twoPointers * 2).toFixed(0);
167 | })()
168 | },
169 | {
170 | name: 'FG%',
171 | default: (function() {
172 | let onePointers = aggregated['1P'] || 0;
173 | let twoPointers = aggregated['2P'] || 0;
174 | let onePointAttempts = aggregated['1PA'] || 0;
175 | let twoPointAttempts = aggregated['2PA'] || 0;
176 |
177 | let successes = onePointers + twoPointers;
178 | let attempts = onePointAttempts + twoPointAttempts;
179 | let rate = successes / ( successes + attempts );
180 |
181 | if (isNaN(rate)) {
182 | return '-';
183 | }
184 |
185 | return (rate * 100).toFixed(0) + '%';
186 | })()
187 | },
188 | ];
189 | return aggregatedStats;
190 | },
191 | });
192 |
193 |
194 | Stat.connection = realtimeRestModel({
195 | Map: Stat,
196 | List: Stat.List,
197 | url: {
198 | resource: "/services/stats",
199 | contentType: "application/x-www-form-urlencoded"
200 | },
201 | name: "stat",
202 | queryLogic: new QueryLogic(Stat, bookshelfService),
203 | updateInstanceWithAssignDeep: true
204 | });
205 |
206 |
207 | export default Stat;
208 |
--------------------------------------------------------------------------------
/public/models/stat_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from "steal-qunit";
2 | import Stat from './stat';
3 | import stats from 'bitballs/models/fixtures/stats';
4 |
5 | QUnit.module('bitballs/models/tournament', {
6 | setup: function () {
7 | }
8 | });
9 |
10 | QUnit.test('It should return a list of aggregated stats', function () {
11 | let statList = new Stat.List(stats);
12 | let aggregatedStats = statList.aggregated;
13 | QUnit.deepEqual(aggregatedStats[0], {name: '1P', default: "20"});
14 | QUnit.deepEqual(aggregatedStats[1], {name: '1PA', default: "30"});
15 | QUnit.deepEqual(aggregatedStats[2], {name: '2P', default: "9"});
16 | });
17 |
--------------------------------------------------------------------------------
/public/models/team.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {can-map} bitballs/models/team Team
3 | * @parent bitballs.clientModels
4 | *
5 | * @group bitballs/models/team.properties 0 properties
6 | */
7 | import { DefineMap, DefineList, superModel, QueryLogic } from "can";
8 | import bookshelfService from "./bookshelf-service";
9 | import Player from "./player";
10 |
11 | var Team = DefineMap.extend('Team', {
12 | /**
13 | * @property {Array}
14 | * A list of available team colors.
15 | **/
16 | colors: ["Black","White","Red","Green","Blue","Yellow","Brown","Gray","Orange","Purple"]
17 | },
18 | {
19 | /**
20 | * @property {Number} bitballs/models/team.properties.id id
21 | * @parent bitballs/models/team.properties
22 | *
23 | * A unique identifier.
24 | **/
25 | id: {type: 'number', identity: true},
26 | /**
27 | * @property {Number} bitballs/models/team.properties.tournamentId tournamentId
28 | * @parent bitballs/models/team.properties
29 | *
30 | * The `id` of [bitballs/models/tournament] that the team will be
31 | * associated with.
32 | **/
33 | tournamentId: "number",
34 | /**
35 | * @property {bitballs/models/player} bitballs/models/team.properties.player1 player1
36 | * @parent bitballs/models/team.properties
37 | *
38 | * A reference to a [bitballs/models/player] model.
39 | **/
40 | player1: Player,
41 | /**
42 | * @property {bitballs/models/player} bitballs/models/team.properties.player2 player2
43 | * @parent bitballs/models/team.properties
44 | *
45 | * A reference to a [bitballs/models/player] model.
46 | **/
47 | player2: Player,
48 | /**
49 | * @property {bitballs/models/player} bitballs/models/team.properties.player3 player3
50 | * @parent bitballs/models/team.properties
51 | *
52 | * A reference to a [bitballs/models/player] model.
53 | **/
54 | player3: Player,
55 | /**
56 | * @property {bitballs/models/player} bitballs/models/team.properties.player4 player4
57 | * @parent bitballs/models/team.properties
58 | *
59 | * A reference to a [bitballs/models/player] model.
60 | **/
61 | player4: Player,
62 | /**
63 | * @property {String} bitballs/models/team.properties.name name
64 | * @parent bitballs/models/team.properties
65 | *
66 | * Name of the team
67 | **/
68 | name: 'string',
69 | /**
70 | * @property {String} bitballs/models/team.properties.color color
71 | * @parent bitballs/models/team.properties
72 | *
73 | * Team color
74 | **/
75 | color: 'string',
76 | /**
77 | * @property {Number} bitballs/models/team.properties.player1Id player1Id
78 | * @parent bitballs/models/team.properties
79 | *
80 | * id of the player 1.
81 | **/
82 | player1Id: 'number',
83 | /**
84 | * @property {Number} bitballs/models/team.properties.player2Id player1Id
85 | * @parent bitballs/models/team.properties
86 | *
87 | * id of the player 2.
88 | **/
89 | player2Id: 'number',
90 | /**
91 | * @property {Number} bitballs/models/team.properties.player3Id player1Id
92 | * @parent bitballs/models/team.properties
93 | *
94 | * id of the player 3.
95 | **/
96 | player3Id: 'number',
97 | /**
98 | * @property {Number} bitballs/models/team.properties.player4Id player1Id
99 | * @parent bitballs/models/team.properties
100 | *
101 | * id of the player 4.
102 | **/
103 | player4Id: 'number',
104 | /**
105 | * @property {bitballs/models/player.static.List} bitballs/models/team.properties.players players
106 | * @parent bitballs/models/team.properties
107 | *
108 | * A list made up of the [bitballs/models/player] models referenced
109 | * by properties [bitballs/models/team.properties.player1],
110 | * [bitballs/models/team.properties.player2], [bitballs/models/team.properties.player3],
111 | * and [bitballs/models/team.properties.player4].
112 | **/
113 | get players() {
114 | var players = [],
115 | self = this;
116 | ["player1","player2","player3","player4"].map(function(name){
117 | if(self[name]) {
118 | players.push(self[name]);
119 | }
120 | });
121 | return new Player.List(players);
122 | }
123 | });
124 | /**
125 | * @constructor {can-list} bitballs/models/team.static.List List
126 | * @parent bitballs/models/team.static
127 | */
128 | Team.List = DefineList.extend('TeamsList',
129 | /** @prototype **/
130 | {
131 | "#": Team,
132 | /**
133 | * @property {Object}
134 | *
135 | * A map of team ids to [bitballs/models/team] models.
136 | **/
137 | get idMap() {
138 | var map = {};
139 |
140 | this.forEach(function(team){
141 | map[team.id] = team;
142 | });
143 |
144 | return map;
145 | },
146 | /**
147 | * @function
148 | *
149 | * Iterates the list of the [bitballs/models/team] models and removes the
150 | * [bitballs/models/team] with the specified `id`.
151 | *
152 | * @param {Number} id
153 | **/
154 | removeById: function(id){
155 | var i = 0;
156 | while(i < this.length) {
157 | if(this[i].id === id) {
158 | this.splice(i, 1);
159 | } else {
160 | i++;
161 | }
162 | }
163 | },
164 | /**
165 | * @function
166 | * Returns a Team in the list of teams given its id.
167 | * @param {Number} id
168 | * @return {bitballs/models/team|undefined} The team if it exists.
169 | */
170 | getById: function(id){
171 | return this.idMap[id];
172 | }
173 | });
174 |
175 | superModel({
176 | Map: Team,
177 | List: Team.List,
178 | url: {
179 | resource: "/services/teams",
180 | contentType: "application/x-www-form-urlencoded"
181 | },
182 | name: "team",
183 | queryLogic: new QueryLogic(Team, bookshelfService),
184 | updateInstanceWithAssignDeep: true
185 | });
186 |
187 | export default Team;
188 |
--------------------------------------------------------------------------------
/public/models/test.html:
--------------------------------------------------------------------------------
1 | Bitballs Model Tests
2 |
3 |
--------------------------------------------------------------------------------
/public/models/test.js:
--------------------------------------------------------------------------------
1 | import './fixtures/';
2 | import './tournament_test';
3 | import './stat_test';
4 |
--------------------------------------------------------------------------------
/public/models/tournament.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {can-map} bitballs/models/tournament Tournament
3 | * @parent bitballs.clientModels
4 | *
5 | * @group bitballs/models/tournament.properties 0 properties
6 | */
7 | import {
8 | superModel, QueryLogic, DefineMap, DefineList
9 | } from "can";
10 | import bookshelfService from "./bookshelf-service";
11 | import moment from "moment";
12 |
13 |
14 | var Tournament = DefineMap.extend('Tournament', {
15 | /**
16 | * @property {Number} bitballs/models/tournament.properties.id id
17 | * @parent bitballs/models/tournament.properties
18 | *
19 | * A unique identifier.
20 | **/
21 | id: {type: 'number', identity: true},
22 | /**
23 | * @property {String} bitballs/models/tournament.properties.date date
24 | * @parent bitballs/models/tournament.properties
25 | *
26 | * The date that the tournament is schedule to occur.
27 | **/
28 | date: 'string',
29 | /**
30 | * @property {Date} bitballs/models/tournament.properties.jsDate jsDate
31 | * @parent bitballs/models/tournament.properties
32 | *
33 | * The [bitballs/models/tournament.properties.date] converted to a
34 | * JavaScript Date object.
35 | **/
36 | get jsDate() {
37 | return moment(this.date).toDate() || null;
38 | },
39 | /**
40 | * @property {Date} bitballs/models/tournament.properties.year year
41 | * @parent bitballs/models/tournament.properties
42 | *
43 | * The year referred to by [bitballs/models/tournament.properties.jsDate].
44 | **/
45 | get year() {
46 | var jsDate = this.jsDate;
47 | return jsDate ? jsDate.getFullYear() : null;
48 | },
49 | /**
50 | * @property {Date} bitballs/models/tournament.properties.prettyDate prettyDate
51 | * @parent bitballs/models/tournament.properties
52 | *
53 | * A formatted output of [bitballs/models/tournament.properties.date].
54 | **/
55 | get prettyDate() {
56 | var date = new Date(this.date);
57 | return isNaN(date) ? null : date;
58 | }
59 | });
60 |
61 | /**
62 | * @constructor {can-list} bitballs/models/tournament.static.List List
63 | * @parent bitballs/models/tournament.static
64 | */
65 | Tournament.List = DefineList.extend('TournamentList', {"#": Tournament});
66 |
67 |
68 | Tournament.connection = superModel({
69 | Map: Tournament,
70 | List: Tournament.List,
71 | url: {
72 | resource: "/services/tournaments",
73 | contentType: "application/x-www-form-urlencoded"
74 | },
75 | name: "tournament",
76 | queryLogic: new QueryLogic(Tournament, bookshelfService),
77 | updateInstanceWithAssignDeep: true
78 | });
79 |
80 | export default Tournament;
81 |
--------------------------------------------------------------------------------
/public/models/tournament_test.js:
--------------------------------------------------------------------------------
1 | import QUnit from "steal-qunit";
2 | import Tournament from './tournament';
3 |
4 | QUnit.module('bitballs/models/tournament', {
5 | setup: function () {
6 | this.tournament = new Tournament();
7 | }
8 | });
9 |
10 | QUnit.test('Year does not get improperly displayed based on time zone', function () {
11 | var tournament = this.tournament;
12 | tournament.date = '2016-01-01';
13 | QUnit.equal(tournament.year, '2016');
14 |
15 | });
16 |
--------------------------------------------------------------------------------
/public/models/user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {can-map} bitballs/models/user User
3 | * @parent bitballs.clientModels
4 | *
5 | * @group bitballs/models/user.static 0 static
6 | *
7 | * A [can.Map](https://canjs.com/docs/can.Map.html) that's connected to the [services/users] with
8 | * all of [can-connect/can/super-map](https://connect.canjs.com/doc/can-connect%7Ccan%7Csuper-map.html)'s
9 | * behaviors.
10 | *
11 | * @body
12 | *
13 | * ## Use
14 | *
15 | * Use the `User` model to CRUD users on the server. Use the CRUD methods `getList`, `save`, and `destroy` added to
16 | * `User` by the [can-connect/can/map](https://connect.canjs.com/doc/can-connect%7Ccan%7Cmap.html) behavior.
17 | *
18 | *
19 | * ```
20 | * var User = require("bitballs/models/user");
21 | * User.getList({where: {gameId: 5 }}).then(function(users){ ... });
22 | * new User({gameId: 6, playerId: 15, type: "1P", time: 60}).save()
23 | * ```
24 | */
25 | import { superModel, QueryLogic, DefineMap, DefineList } from "can";
26 | import bookshelfService from "./bookshelf-service";
27 |
28 |
29 | var User = DefineMap.extend('User', {
30 | /**
31 | * @property {Number} bitballs/models/user.properties.id id
32 | * @parent bitballs/models/user.properties
33 | *
34 | * A unique identifier.
35 | **/
36 | id: {type: 'number', identity: true},
37 | /**
38 | * @property {String} bitballs/models/user.properties.email email
39 | * @parent bitballs/models/user.properties
40 | *
41 | * Email address representing the user
42 | **/
43 | email: 'string',
44 | /**
45 | * @property {String} bitballs/models/user.properties.password password
46 | * @parent bitballs/models/user.properties
47 | *
48 | * Password for the user
49 | **/
50 | password: 'string',
51 |
52 | /**
53 | * @property {String} bitballs/models/user.properties.newPassword newPassword
54 | * @parent bitballs/models/user.properties
55 | *
56 | * A placeholder for the user's new password.
57 | **/
58 | newPassword: 'string',
59 | /**
60 | * @property {String} bitballs/models/user.properties.name name
61 | * @parent bitballs/models/user.properties
62 | *
63 | * User's full name as returned by the server
64 | **/
65 | name: 'string',
66 | /**
67 | * @property {Boolean} bitballs/models/user.properties.isAdmin isAdmin
68 | * @parent bitballs/models/user.properties
69 | *
70 | * A boolean representing if the user has admin rights
71 | **/
72 | isAdmin: 'boolean',
73 | /**
74 | * @property {Boolean} bitballs/models/user.properties.verified verified
75 | * @parent bitballs/models/user.properties
76 | *
77 | * A boolean representing if the user is verified
78 | **/
79 | verified: 'boolean',
80 | /**
81 | * @property {String} bitballs/models/user.properties.verificationHash verificationHash
82 | * @parent bitballs/models/user.properties
83 | *
84 | * A unique hash representing user verification
85 | **/
86 | verificationHash: 'string'
87 | });
88 |
89 | /**
90 | * @constructor {can-list} bitballs/models/user.static.List List
91 | * @parent bitballs/models/user.static
92 | */
93 | User.List = DefineList.extend('UserList', {"#": User});
94 |
95 | User.queryLogic = new QueryLogic(User, bookshelfService);
96 |
97 | User.connection = superModel({
98 | Map: User,
99 | List: User.List,
100 | url: {
101 | resource: "/services/users",
102 | contentType: "application/x-www-form-urlencoded"
103 | },
104 | name: "user",
105 | queryLogic: User.queryLogic,
106 | updateInstanceWithAssignDeep: true
107 | });
108 |
109 |
110 | export default User;
111 |
--------------------------------------------------------------------------------
/public/models/youtube.js:
--------------------------------------------------------------------------------
1 | var platform = require("steal-platform" );
2 |
3 | var promise;
4 |
5 | module.exports = function(){
6 | if(promise) {
7 | return promise;
8 | } else {
9 | return promise = new Promise(function(resolve, reject){
10 | if ( platform.isNode ) {
11 | reject({});
12 | return;
13 | }
14 | window.onYouTubeIframeAPIReady = function(){
15 | resolve(YT);
16 | };
17 | var tag = document.createElement('script');
18 |
19 | tag.src = "https://www.youtube.com/iframe_api";
20 | document.head.appendChild(tag);
21 | });
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/public/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitballs",
3 | "version": "0.4.1",
4 | "description": "",
5 | "homepage": "",
6 | "author": "Bitovi",
7 | "scripts": {
8 | "install": "node build.js",
9 | "test": "rm -rf ~/.mozilla && testee test.html --browsers firefox --reporter Spec"
10 | },
11 | "main": "bitballs/index.stache!done-autorender",
12 | "files": [
13 | "."
14 | ],
15 | "keywords": [],
16 | "license": "MIT",
17 | "dependencies": {
18 | "bootstrap": "^3.3.7",
19 | "can-assign": "^1.1.1",
20 | "can": "^5.0.0",
21 | "can-debug": "^2.0.1",
22 | "can-route-pushstate": "^5.0.7",
23 | "can-stache-route-helpers": "^1.1.1",
24 | "can-view-autorender": "^5.0.0",
25 | "can-zone": "^1.0.0",
26 | "done-autorender": "^2.6.3",
27 | "done-component": "^2.2.0",
28 | "done-css": "^3.0.2",
29 | "done-serve": "^3.0.0-pre.3",
30 | "done-ssr-middleware": "^3.0.0-pre.0",
31 | "funcunit": "^3.5.0",
32 | "generator-donejs": "3.0.0-pre.2",
33 | "jquery": "^3.1.1",
34 | "moment": "^2.10.6",
35 | "steal": "^2.1.5",
36 | "steal-less": "^1.3.4",
37 | "steal-platform": "0.0.4",
38 | "steal-qunit": "^1.0.1",
39 | "steal-stache": "^4.1.2",
40 | "steal-tools": "^2.0.6",
41 | "yeoman-environment": "^1.2.7"
42 | },
43 | "devDependencies": {
44 | "can-route-hash": "^1.0.1",
45 | "donejs-cli": "^3.0.0-pre.3",
46 | "firebase-tools": "^3.9.1",
47 | "steal-conditional": "^1.1.1",
48 | "testee": "^0.8.0"
49 | },
50 | "steal": {
51 | "configDependencies": [
52 | "live-reload",
53 | "node_modules/can-zone/register",
54 | "node_modules/steal-conditional/conditional"
55 | ],
56 | "envs": {
57 | "server-production": {
58 | "renderingBaseURL": "https://bitballs-e69ca.firebaseapp.com/"
59 | }
60 | },
61 | "meta": {
62 | "bootstrap/js/dropdown": {
63 | "deps": [
64 | "jquery"
65 | ]
66 | }
67 | },
68 | "bundle": [
69 | "bitballs/components/game/details/",
70 | "bitballs/components/tournament/details/",
71 | "bitballs/components/tournament/list/",
72 | "bitballs/components/user/details/",
73 | "bitballs/components/user/list/",
74 | "bitballs/components/game/details/",
75 | "bitballs/components/player/list/",
76 | "bitballs/components/player/details/",
77 | "bitballs/components/404.component!"
78 | ],
79 | "plugins": [
80 | "done-component",
81 | "done-css",
82 | "steal-less",
83 | "steal-stache"
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/public/prod.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/service.js:
--------------------------------------------------------------------------------
1 | var ssr = require('done-ssr-middleware');
2 |
3 | module.exports = ssr({
4 | config: __dirname + "/package.json!npm",
5 | main: "bitballs/index.stache!done-autorender",
6 | liveReload: true
7 | }, {
8 | strategy: "incremental"
9 | });
10 |
--------------------------------------------------------------------------------
/public/test.html:
--------------------------------------------------------------------------------
1 | bitballs-client tests
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/public/test.js:
--------------------------------------------------------------------------------
1 | import './models/test';
2 | import './components/test';
3 | import './util/test';
4 |
--------------------------------------------------------------------------------
/public/test/utils.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import F from 'funcunit';
3 |
4 | export default {
5 | insertAndPopulateIframe: function (iframeParentSelector, frag) {
6 | var localStyles = $('head style').clone();
7 | var iframe = $(document.createElement('iframe'));
8 | var iframeWindow;
9 |
10 | // Insert the iframe
11 | $(iframeParentSelector).append(iframe);
12 |
13 | // Get the context of the iframe (now that it's been added to the DOM)
14 | iframeWindow = iframe[0].contentWindow;
15 |
16 | // Populate the iframe
17 | iframe.contents().find('body').append(frag);
18 | iframe.contents().find('head').append(localStyles);
19 |
20 | // Convince FuncUnit that the iframe is loaded
21 | // (https://github.com/bitovi/funcunit/issues/139)
22 | F.documentLoaded = function () { return true; };
23 |
24 | // Set the context of FuncUnit to be the iframe
25 | F.open(iframeWindow);
26 | }
27 | };
--------------------------------------------------------------------------------
/public/util/prefilter.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | export default $.ajaxPrefilter(function(options, originalOptions, jqXHR){
4 | if(options.type === "POST" || options.type === "PUT"){
5 | options.data = JSON.stringify(originalOptions.data);
6 | options.contentType = "application/json";
7 | options.dataType = "json";
8 | }
9 | });
--------------------------------------------------------------------------------
/public/util/test.html:
--------------------------------------------------------------------------------
1 | util
2 |
3 |
--------------------------------------------------------------------------------
/public/util/test.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/donejs/bitballs/9670ae729d4cbfe0900d20e654d2bf96aa402c6a/public/util/test.js
--------------------------------------------------------------------------------
/services/adminOnly.js:
--------------------------------------------------------------------------------
1 | module.exports = function ( respObj, status ) {
2 | if ( typeof respObj === "string" ) {
3 | respObj = {
4 | message: respObj
5 | };
6 | }
7 | return function ( req, res, next ) {
8 | if ( req.isAdmin ) {
9 | next();
10 | } else {
11 | if ( respObj ) {
12 | res.status( status || 401 ).json( respObj );
13 | } else {
14 | res.status( status || 404 ).end();
15 | }
16 | }
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/services/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var bodyParser = require('body-parser');
3 | var app = express();
4 |
5 | app.use(bodyParser.urlencoded({ extended: false }));
6 |
7 | // parse application/json
8 | app.use(bodyParser.json());
9 |
10 | module.exports = app;
11 |
--------------------------------------------------------------------------------
/services/email.js:
--------------------------------------------------------------------------------
1 | var nodemailer = require('nodemailer');
2 | //var checkit = require('checkit');
3 |
4 | var transportOpts;
5 |
6 | if ( process.argv.indexOf( "--develop" ) !== -1 ) {
7 | var MailDev = require('maildev');
8 | var maildev = new MailDev({ smtp: 1025 });
9 | maildev.listen();
10 | maildev.on( "new", function ( email ) {
11 | console.log( "email captured: http://localhost:1080" );
12 | });
13 |
14 | transportOpts = {
15 | port: 1025,
16 | ignoreTLS: true
17 | };
18 | } else {
19 | transportOpts = process.env.EMAIL_CONFIG;
20 | }
21 |
22 | module.exports = function ( to, from, subject, body, cb ) {
23 |
24 | var transporter = nodemailer.createTransport( transportOpts );
25 |
26 | return transporter.sendMail({
27 | from: from,
28 | bcc: Array.isArray( to ) ? to.join( "," ) : to,
29 | subject: subject,
30 | html: body
31 | }, cb );
32 | };
33 |
--------------------------------------------------------------------------------
/services/games.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {function} services/games /services/games
3 | * @parent bitballs.services
4 | *
5 | * @signature `GET /services/games`
6 | * Gets games from the database.
7 | *
8 | * GET /services/games?
9 | * where[tournamentId]=5&
10 | * withRelated[]=homeTeam&withRelated[]=awayTeam&
11 | * sortBy=round
12 | *
13 | * @param {Object} [where] Clause used to filter which games are returned.
14 | * @param {Array} [withRelated] Clause used to add related data.
15 | * @param {String} [sortBy] Clause used to sort the returned games.
16 | * @return {connectData} An object that contains the games:
17 | *
18 | * {data: [{
19 | * id: Int,
20 | * tournamentId: Int,
21 | * round: String,
22 | * court: String,
23 | * videoUrl: String,
24 | * homeTeamId: Int,
25 | * awayTeamId: Int
26 | * }, ...]}
27 | *
28 | * @signature `POST /services/games`
29 | * Creates a game in the database. Only admins are allowed to create games.
30 | *
31 | * POST /services/games
32 | * {
33 | * "tournamentId": 1,
34 | * "round": 'Final',
35 | * "court": 'Old court',
36 | * "videoUrl": '?v=2141232213',
37 | * "homeTeamId": 1
38 | * "awayTeamId": 1
39 | * }
40 | *
41 | * @param {JSON} JSONBody The raw JSON properties of a game object
42 | * @return {JSON} Returns JSON with all the properties of the newly created object, including its id.
43 | *
44 | * {
45 | * "id": 9,
46 | * "tournamentId": 1,
47 | * "round": 'Final',
48 | * "court": 'Old court',
49 | * "videoUrl": '?v=2141232213',
50 | * "homeTeamId": 1
51 | * "awayTeamId": 1
52 | * }
53 | *
54 | *
55 | * @signature `GET /services/games/:id`
56 | * Gets a game by id from the database
57 | *
58 | * GET /services/games/9?
59 | * withRelated[]=homeTeam
60 | *
61 | * @param {Array} [withRelated] Clause used to add related data.
62 | * @return {JSON} An object that contains the game data:
63 | *
64 | * {
65 | * id: Int,
66 | * tournamentId: Int,
67 | * round: String,
68 | * court: String,
69 | * videoUrl: String,
70 | * homeTeamId: Int,
71 | * awayTeamId: Int
72 | * }
73 | *
74 | * @signature `PUT /services/games/:id`
75 | * Updates a game in the database. Only admins are allowed to update games.
76 | *
77 | * PUT /services/games/9
78 | * {
79 | * "tournamentId": 1,
80 | * "round": 'Final',
81 | * "court": 'New court',
82 | * "videoUrl": '?v=2141232213',
83 | * "homeTeamId": 1
84 | * "awayTeamId": 1
85 | * }
86 | *
87 | * @param {JSON} JSONBody The updated properties of the game object
88 | * @return {JSON} Returns JSON with all the properties of the updated object, including its id.
89 | *
90 | * {
91 | * "id": 9,
92 | * "tournamentId": 1,
93 | * "round": 'Final',
94 | * "court": 'New court',
95 | * "videoUrl": '?v=2141232213',
96 | * "homeTeamId": 1
97 | * "awayTeamId": 1
98 | * }
99 | *
100 | *
101 | * @signature `DELETE /services/games`
102 | * Deletes a game in the database. Only admins are allowed to delete games.
103 | *
104 | * DELETE /services/games/9
105 | *
106 | * @return {JSON} Returns an empty JSON object.
107 | *
108 | * {}
109 | */
110 |
111 | var app = require("./app");
112 | var Game = require("../models/game");
113 | var adminOnly = require( "./adminOnly" );
114 | var separateQuery = require("./separate-query");
115 |
116 | app.get('/services/games', function(req, res){
117 | var q = separateQuery(req.query),
118 | query = q.query,
119 | fetch = q.fetch;
120 | Game.collection().query(query).fetch(fetch).then(function(games){
121 | res.send({data: games.toJSON()});
122 | });
123 | });
124 |
125 | app.get('/services/games/:id', function(req, res){
126 | var q = separateQuery(req.query),
127 | query = q.query,
128 | fetch = q.fetch;
129 | new Game({id: req.params.id}).query(query).fetch(fetch).then(function(game){
130 | if(game){
131 | res.send(game.toJSON());
132 | }else {
133 | res.status(404).send();
134 | }
135 | });
136 | });
137 |
138 | app.put('/services/games/:id', adminOnly( "Must be an admin to update games" ), function(req, res){
139 | new Game({id: req.params.id}).save(req.body).then(function(game){
140 | res.send(game.toJSON());
141 | });
142 | });
143 |
144 | app.delete('/services/games/:id', adminOnly( "Must be an admin to delete games" ), function(req, res){
145 | new Game({id: req.params.id}).destroy().then(function(game){
146 | res.send({});
147 | });
148 | });
149 |
150 | app.post('/services/games', adminOnly( "Must be an admin to create games" ), function(req, res) {
151 | new Game(req.body).save().then(function(game){
152 | res.send({id: game.get('id')});
153 | }, function(e){
154 | res.status(500).send(e);
155 | });
156 | });
157 |
158 | module.exports = Game;
159 |
--------------------------------------------------------------------------------
/services/players.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {function} services/players /services/players
3 | * @parent bitballs.services
4 | *
5 | * @signature `GET /services/players`
6 | * Gets players from the database
7 | *
8 | * GET /services/games?
9 | * where[playerId]=5&
10 | * sortBy=startRank
11 | *
12 | * @param {Object} [where] Clause used to filter which players are returned.
13 | * @param {String} [sortBy] Clause used to sort the returned players
14 | * @return {JSON} An object that contains the player data:
15 | *
16 | * {data: [{
17 | * id: Int,
18 | * name: String, // Player name
19 | * weight: Int, // Player weight, in lbs
20 | * height: Int, // Player height, in inches
21 | * birthday: Date, // Player birthday
22 | * profile: String, // Player description/bio
23 | * startRank: String // Starting Rank
24 | * }]}
25 | *
26 | * @signature `POST /services/players`
27 | * Adds a player to the database. Only admins are allowed to create players.
28 | *
29 | * POST /services/players
30 | * {
31 | * "name": "Harper Lee",
32 | * "weight": 190,
33 | * "height": 72,
34 | * "birthday": "1990-01-22",
35 | * "profile": "Author of 'To Kill a Mockingbird'",
36 | * "startRank": "novice"
37 | * }
38 | *
39 | * @param {JSON} JSONBody The Raw JSON properties of a player object
40 | * @return {JSON} Returns JSON with all the properties of the newly created object, including its id
41 | *
42 | * {
43 | * "id": 9,
44 | * "name": "Harper Lee",
45 | * "weight": 190,
46 | * "height": 72,
47 | * "birthday": "1990-01-22",
48 | * "profile": "Author of 'To Kill a Mockingbird'",
49 | * "startRank": "novice"
50 | * }
51 | *
52 | * @signature `GET /services/players/:id`
53 | * Gets a player by id from the database.
54 | *
55 | * GET /services/players/9
56 | *
57 | * @return {JSON} An object that contains the player data:
58 | *
59 | * {data: [{
60 | * id: Int,
61 | * name: String, // Player name
62 | * weight: Int, // Player weight, in lbs
63 | * height: Int, // Player height, in inches
64 | * birthday: Date // Player birthday
65 | * profile: String // Player description/bio
66 | * startRank: String // Starting Rank
67 | * }]}
68 | *
69 | * @signature `PUT /services/players/:id`
70 | * Updates a player in the database. Only admins are allowed to update players.
71 | *
72 | * PUT /services/players/9
73 | * {
74 | * "name": "Harper Lee",
75 | * "weight": 190,
76 | * "height": 72,
77 | * "birthday": "1990-01-22",
78 | * "profile": "Author of 'To Kill a Mockingbird' and `Absalom, Absalom`",
79 | * "startRank": "novice"
80 | * }
81 | *
82 | * @param {JSON} JSONBody The updated properties of the player object
83 | * @return {JSON} Returns JSON with all the properties of the updated object, including its id.
84 | *
85 | * {
86 | * "name": "Harper Lee",
87 | * "weight": 190,
88 | * "height": 72,
89 | * "birthday": "1990-01-22",
90 | * "profile": "Author of 'To Kill a Mockingbird' and `Absalom, Absalom`",
91 | * "startRank": "novice"
92 | * }
93 | *
94 | * @signature `DELETE /services/players/:id`
95 | * Deletes a player in the database. Only admins are allowed to delete players.
96 | *
97 | * DELETE /services/players/9
98 | *
99 | * @return {JSON} Returns and empty JSON object.
100 | *
101 | * {}
102 | */
103 |
104 | var app = require("./app");
105 | var Player = require("../models/player");
106 | var adminOnly = require( "./adminOnly" );
107 | var separateQuery = require("./separate-query");
108 |
109 | app.get('/services/players', function(req, res){
110 | var q = separateQuery(req.query),
111 | query = q.query,
112 | fetch = q.fetch;
113 | Player.collection().query(query).fetch(fetch).then(function(players){
114 | res.send({data: players.toJSON()});
115 | });
116 | });
117 |
118 | app.get('/services/players/:id', function(req, res){
119 | var q = separateQuery(req.query),
120 | query = q.query,
121 | fetch = q.fetch;
122 | new Player({id: req.params.id}).query(query).fetch(fetch).then(function(player){
123 | res.send(player.toJSON());
124 | });
125 | });
126 |
127 | app.put('/services/players/:id', adminOnly( "Must be an admin to update players" ), function(req, res){
128 | var cleaned = clean(req.body);
129 | new Player({id: req.params.id}).save(cleaned).then(function(player){
130 | res.send(player.toJSON());
131 | });
132 | });
133 |
134 | app.delete('/services/players/:id', adminOnly( "Must be an admin to delete players" ), function(req, res){
135 | new Player({id: req.params.id}).destroy().then(function(player){
136 | res.send({_destroyed: true});
137 | });
138 | });
139 |
140 | app.post('/services/players', adminOnly( "Must be an admin to create players" ), function(req, res) {
141 | new Player(clean(req.body)).save().then(function(player){
142 | res.send({id: player.get('id')});
143 | }, function(e){
144 | res.status(500).send(e);
145 | });
146 | });
147 |
148 | module.exports = Player;
149 |
150 | var clean = function(data){
151 | if(data.name === ''){
152 | delete data.name;
153 | }
154 |
155 | if(data.weight) {
156 | data.weight = parseInt(data.weight, 10);
157 | }
158 |
159 | if(data.height) {
160 | data.height = parseInt(data.height, 10);
161 | }
162 |
163 | return data;
164 | };
165 |
--------------------------------------------------------------------------------
/services/separate-query.js:
--------------------------------------------------------------------------------
1 | var fetchKeys = [ 'require', 'columns', 'transacting', 'withRelated' ];
2 |
3 | module.exports = function(input) {
4 | var output = {
5 | query: {},
6 | fetch: {},
7 | };
8 |
9 | for (var key in input) {
10 | if (fetchKeys.indexOf(key) >= 0) {
11 | output.fetch[key] = input[key];
12 | }
13 | else {
14 | output.query[key] = input[key];
15 | }
16 | }
17 |
18 | return output;
19 | };
20 |
--------------------------------------------------------------------------------
/services/session.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {function} services/session /services/session
3 | * @parent bitballs.services
4 | *
5 | * @signature `GET /services/session`
6 | * Gets the current session, if any.
7 | *
8 | * GET /services/session
9 | *
10 | * @return {JSON} An object containing the user object with sensitive properties omitted.
11 | *
12 | * {
13 | * user: {
14 | * "id": Int,
15 | * "name": String, // Optional name
16 | * "email": String, // User email address
17 | * "isAdmin": Boolean, // Whether user is an admin
18 | * "verified": Boolean // Whether user has verified an email address
19 | * }
20 | * }
21 | *
22 | * @signature `POST /services/session`
23 | * If a user object is provided with a valid password/email combination, logs in the current user and creates a session.
24 | *
25 | * POST /services/session
26 | * {
27 | * user: {
28 | * {
29 | * "email": "addyfizzle@publicdefenders.org"
30 | * "password": "H3HLJ2HIO4"
31 | * }
32 | * }
33 | * }
34 | *
35 | * @return {JSON} An object containing the logged in user object with sensitive properties omitted.
36 | *
37 | * {
38 | * user: {
39 | * "id": 9,
40 | * "name": "Atticus Finch",
41 | * "email": "addyfizzle@publicdefenders.org",
42 | * "isAdmin": false,
43 | * "verified": true
44 | * }
45 | * }
46 | *
47 | * @signature `DELETE /services/session`
48 | * Logs the current user out.
49 | *
50 | * DELETE /services/session
51 | *
52 | * @return {JSON} Returns an empty JSON object.
53 | *
54 | * {}
55 | */
56 |
57 | var app = require("./app");
58 | var User = require("../models/user");
59 | var separateQuery = require("./separate-query");
60 | var _ = require("lodash");
61 | var passport = require('passport');
62 | var bCrypt = require("bcrypt-nodejs");
63 |
64 | passport.serializeUser(function(user, done) {
65 | done(null, user.id);
66 | });
67 |
68 | passport.deserializeUser(function(id, done) {
69 | new User({
70 | 'id' : id
71 | }).fetch().then(function(user) {
72 | done(null, user);
73 | }, function(err) {
74 | done(err);
75 | });
76 | });
77 |
78 | var expressSession = require('express-session');
79 |
80 | var cookieSecret = process.env.COOKIE_SECRET || 'devSecret';
81 |
82 | app.use(expressSession({
83 | secret : cookieSecret,
84 | resave : false,
85 | saveUninitialized : false
86 | }));
87 | app.use(passport.initialize());
88 | app.use(passport.session());
89 |
90 | app.use(function ( req, res, next ) {
91 | req.isAdmin = ( req.user && req.user.attributes && req.user.attributes.isAdmin ) ? true : false;
92 | next();
93 | });
94 |
95 | var isValidPassword = function(user, password) {
96 | return bCrypt.compareSync(password, user.get("password") );
97 | };
98 |
99 | app.get('/services/session', function(req, res) {
100 | if (req.user) {
101 | res.send({id: req.user.id, user: _.omit(req.user.toJSON(), "password")});
102 | } else {
103 | res.status(404).send(JSON.stringify({
104 | message : "No session"
105 | }));
106 | }
107 | });
108 |
109 | app.post('/services/session', function(req, res, next) {
110 | var email = req.body.user.email,
111 | password = req.body.user.password;
112 |
113 | var q = separateQuery(req.query),
114 | query = q.query,
115 | fetch = q.fetch;
116 |
117 | new User({
118 | 'email': email
119 | }).query(query).fetch(fetch).then(function(user) {
120 | if(user && isValidPassword(user, password)) {
121 | req.logIn(user, function(err) {
122 | if (err) {
123 | next(err);
124 | }
125 | return res.json({user: _.omit(req.user.toJSON(), "password")});
126 | });
127 | } else {
128 | // Security conventions dictate that you shouldn't tell the user whether
129 | // it was their username or their password that was the problem
130 | return res.status(401).json({message: "Incorrect username or password"});
131 | }
132 |
133 | }, function(error) {
134 |
135 | console.log('User error ' + email, error);
136 | return res.status( 500 ).json( error );
137 |
138 | });
139 | });
140 |
141 | app['delete']("/services/session", function(req, res){
142 | req.logout();
143 | res.json({});
144 | });
145 |
--------------------------------------------------------------------------------
/services/stats.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {function} services/stats /services/stats
3 | * @parent bitballs.services
4 | *
5 | * @signature `GET /services/stats`
6 | * Gets stats from the database.
7 | *
8 | * GET /services/stats?
9 | * where[gameId]=5&
10 | * withRelated[]=player
11 | * sortBy=time
12 | *
13 | * @param {Object} [where] Clause used to filter which stats are returned.
14 | * @param {Array} [withRelated] Clause used to add related data.
15 | * @param {String} [sortBy] Clause used to sort the returned stats.
16 | * @return {JSON} An object that contains the stats:
17 | *
18 | * {data: [{
19 | * id: Int,
20 | * gameId: Int, // Related game
21 | * playerId: Int, // Related player
22 | * type: String, // "1P", "1PA", etc
23 | * time: Int, // time of stat in seconds
24 | * default: Int, // Not currently used, but available.
25 | * }, ...]}
26 | *
27 | * @signature `POST /services/stats`
28 | * Creates a stat in the database. Only admins are allowed to create stats.
29 | *
30 | * POST /services/stats
31 | * {
32 | * "gameId": 6,
33 | * "playerId": 15,
34 | * "type": "1P",
35 | * "time": 60
36 | * }
37 | * @param {JSON} JSONBody The raw JSON properties of a stat object.
38 | * @return {JSON} Returns JSON with all the properties of the newly created object, including its id.
39 | *
40 | * {
41 | * "id": 9
42 | * "gameId": 6,
43 | * "playerId": 15,
44 | * "type": "1P",
45 | * "time": 60
46 | * }
47 | *
48 | * @signature `GET /services/stats/:id`
49 | * Gets a stat by id from the database.
50 | *
51 | * GET /services/stats/5?
52 | * withRelated[]=player
53 | *
54 | * @param {Array} [withRelated] Clause used to add related data.
55 | * @return {JSON} An object that contains the stats:
56 | *
57 | * {
58 | * id: Int,
59 | * gameId: Int, // Related game
60 | * playerId: Int, // Related player
61 | * type: String, // "1P", "1PA", etc
62 | * time: Int, // time of stat in seconds
63 | * default: Int, // Not currently used, but available.
64 | * }
65 | *
66 | * @signature `PUT /services/stats/:id`
67 | * Updates a stat in the database. Only admins are allowed to update stats.
68 | *
69 | * PUT /services/stats/9
70 | * {
71 | * "gameId": 6,
72 | * "playerId": 15,
73 | * "type": "1P",
74 | * "time": 120
75 | * }
76 | *
77 | * @param {JSON} JSONBody The updated properties of the stat object.
78 | * @return {JSON} Returns JSON with all the properties of the updated object, including its id.
79 | *
80 | * {
81 | * "id": 9
82 | * "gameId": 6,
83 | * "playerId": 15,
84 | * "type": "1P",
85 | * "time": 60
86 | * }
87 | *
88 | * @signature `DELETE /services/stats/:id`
89 | * Deletes a stat in the database. Only admins are allowed to delete stats.
90 | *
91 | * DELETE /services/stats/9
92 | *
93 | * @return {JSON} Returns an empty JSON object.
94 | *
95 | * {}
96 | */
97 |
98 | var app = require("./app");
99 | var Stat = require("../models/stat");
100 | var adminOnly = require( "./adminOnly" );
101 | var separateQuery = require("./separate-query");
102 |
103 | app.get('/services/stats', function(req, res){
104 | var q = separateQuery(req.query),
105 | query = q.query,
106 | fetch = q.fetch;
107 | Stat.collection().query(query).fetch(fetch).then(function(stats){
108 | res.send({data: stats.toJSON()});
109 | });
110 | });
111 |
112 | app.get('/services/stats/:id', function(req, res){
113 | var q = separateQuery(req.query),
114 | query = q.query,
115 | fetch = q.fetch;
116 | new Stat({id: req.params.id}).query(query).fetch(fetch).then(function(stat){
117 | res.send(stat.toJSON());
118 | });
119 | });
120 |
121 | app.put('/services/stats/:id', adminOnly( "Must be an admin to update stats" ), function(req, res){
122 | new Stat({id: req.params.id}).save(req.body).then(function(stat){
123 | res.send(stat.toJSON());
124 | });
125 | });
126 |
127 | app.delete('/services/stats/:id', adminOnly( "Must be an admin to delete stats" ), function(req, res){
128 | new Stat({id: req.params.id}).destroy().then(function(stat){
129 | res.send({});
130 | });
131 | });
132 |
133 | app.post('/services/stats', adminOnly( "Must be an admin to create stats" ), function(req, res) {
134 | new Stat(clean(req.body)).save().then(function(stat){
135 | res.send({id: stat.get('id')});
136 | }, function(e){
137 | res.status(500).send(e);
138 | });
139 | });
140 |
141 | module.exports = Stat;
142 |
143 | var clean = function(data){
144 | if(data.time) {
145 | data.time = parseInt(data.time, 10);
146 | }
147 |
148 | return data;
149 | };
150 |
--------------------------------------------------------------------------------
/services/tournaments.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module {function} services/tournaments /services/tournaments
3 | * @parent bitballs.services
4 | *
5 | * @signature `GET /services/tournaments`
6 | * Gets tournaments from the database.
7 | *
8 | * GET /services/tournaments?
9 | * where[date]=2016-01-01&
10 | * sortBy=date
11 | *
12 | * @param {Object} [where] Clause used to filter which tournaments are returned
13 | * @param {String} [sortBy] Clause used to sort the returned stats
14 | * @return {JSON} An object that contains the stats:
15 | *
16 | * {data: [{
17 | * id: Int,
18 | * date: Date
19 | * }, ...]}
20 | *
21 | * @signature `POST /services/tournaments`
22 | * Creates a tournament in the database. Only admins are allowed to create tournaments.
23 | *
24 | * POST /services/tournaments
25 | * {
26 | * "date": "2016-01-01"
27 | * }
28 | *
29 | * @param {JSON} JSONBody The raw JSON properties of a tournament object
30 | * @return {JSON} Returns JSON with all the properties of the newly created object, including its id.
31 | *
32 | * {
33 | * "id": 9,
34 | * "date": "2016-01-01"
35 | * }
36 | *
37 | * @signature `GET /services/tournaments/:id`
38 | * Gets a tournament by id from the database.
39 | *
40 | * GET /services/tournaments/9
41 | *
42 | * @return {JSON} An object that contains the tournament data:
43 | *
44 | * {
45 | * id: Int,
46 | * date: Date
47 | * }
48 | *
49 | * @signature `PUT /services/tournaments/:id`
50 | * Updates a tournament in the database. Only admins are allowed to update tournaments.
51 | *
52 | * PUT /services/tournaments/9
53 | * {
54 | * "date": "2015-01-01"
55 | * }
56 | *
57 | * @param {JSON} JSONBody The updated properties of the tournament object.
58 | * @return {JSON} Returns JSON with all the properties of the updated object, including its id.
59 | *
60 | * {
61 | * "id": 9,
62 | * "date": "2015-01-01"
63 | * }
64 | *
65 | * @signature `DELETE /services/tournaments/:id`
66 | * Deletes a tournament in the database. Only admins are allowed to delete stats.
67 | *
68 | * DELETE /services/tournaments/9
69 | *
70 | * @return {JSON} Returns an empty JSON object.
71 | *
72 | * {}
73 | */
74 |
75 | var app = require("./app");
76 | var Tournament = require("../models/tournament");
77 | var adminOnly = require( "./adminOnly" );
78 | var separateQuery = require("./separate-query");
79 |
80 | app.get('/services/tournaments', function(req, res){
81 | var q = separateQuery(req.query),
82 | query = q.query,
83 | fetch = q.fetch;
84 | Tournament.collection().query(query).fetch(fetch).then(function(tournaments){
85 | res.send({data: tournaments.toJSON()});
86 | });
87 | });
88 |
89 | app.get('/services/tournaments/:id', function(req, res){
90 | var q = separateQuery(req.query),
91 | query = q.query,
92 | fetch = q.fetch;
93 | new Tournament({id: req.params.id}).query(query).fetch(fetch).then(function(tournament){
94 | if(tournament){
95 | res.send(tournament.toJSON());
96 | } else {
97 | res.status(404).send();
98 | }
99 | });
100 | });
101 |
102 | app.put('/services/tournaments/:id', adminOnly( "Must be an admin to update tournaments" ), function(req, res){
103 | new Tournament({id: req.params.id}).save(req.body).then(function(tournament){
104 | res.send(tournament.toJSON());
105 | });
106 | });
107 |
108 | app.delete('/services/tournaments/:id', adminOnly( "Must be an admin to delete tournaments" ), function(req, res){
109 | new Tournament({id: req.params.id}).destroy().then(function(tournament){
110 | res.send({});
111 | });
112 | });
113 |
114 | app.post('/services/tournaments', adminOnly( "Must be an admin to create tournaments" ), function(req, res) {
115 | new Tournament(req.body).save().then(function(tournament){
116 | res.send({id: tournament.get('id')});
117 | }, function(e){
118 | res.status(500).send(e);
119 | });
120 | });
121 |
122 | module.exports = Tournament;
123 |
--------------------------------------------------------------------------------
/test-script.md:
--------------------------------------------------------------------------------
1 | # Test Script
2 |
3 | ## Setup
4 |
5 | To clear the database, run:
6 |
7 | ```
8 | ./node_modules/.bin/db-migrate reset
9 | ```
10 |
11 | until you can't run it anymore
12 |
13 | Then run:
14 |
15 | ```
16 | ./node_modules/.bin/db-migrate up
17 | ```
18 |
19 | To run your app so you can see what's going on, run:
20 |
21 | ```
22 | node index.js --develop --slow
23 | ```
24 |
25 | This puts a 1s delay on all responses.
26 |
27 | ## Verify all pages work when there is no data and no one is logged in.
28 |
29 | - Go to every page, make sure things look right.
30 |
31 | ## Create user
32 |
33 | - try to create a user without an `email` or password. Clicking `Register` multiple times should not spawn multiple requests.
34 |
35 |
36 | - try to create a user without a `password`
37 |
38 | - create a user. You should be logged in and the user's email locked.
39 |
40 | ## Create Players
41 |
42 | - Make sure the placeholder text is showing up.
43 | - Create a player with all the right info.
44 | - Create a player w/o a name ... you shouldn't be able to.
45 |
46 |
47 | ## Update a player
48 |
49 | - you should be able to cancel
50 | - you should be able to update
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | - logout and try to log back in
61 |
62 | ## Update user's password
63 |
--------------------------------------------------------------------------------
/theme/static/content_list.js:
--------------------------------------------------------------------------------
1 | steal("can/control","jquery","can/observe",function(Control, $){
2 |
3 | var contentList = function(sections, tag){
4 | var element = $("<"+tag+">");
5 | $.each(sections, function(i, section){
6 | $li = $("");
7 | $a = $("").attr("href","#"+section.id).text(section.text);
8 | element.append( $li.append($a) );
9 |
10 | if(section.sections && section.sections.length) {
11 | $li.append( contentList(section.sections, tag) );
12 | }
13 | });
14 | return element;
15 | };
16 |
17 | return can.Control.extend({
18 | init: function() {
19 |
20 | var sections = [];
21 |
22 | this.collectSignatures().each(function(ix) {
23 | var h2 = $('h2', this);
24 | this.id = 'sig_' + h2.text().replace(/\s/g,"").replace(/[^\w]/g,"_");
25 | //this.id = encodeURIComponent(h2.text());
26 | sections.push({id: this.id, text: h2.text()});
27 | });
28 |
29 | var headingStack = [],
30 | last = function(){
31 | return headingStack[ headingStack.length -1 ]
32 | };
33 |
34 | var ch = this.collectHeadings().each(function(ix) {
35 | var el = $(this);
36 | //change formatting here from default to match this:
37 | // ## This is My Heading -->
38 | this.id = el.text().toLowerCase().replace(/[^\w]/g,"-").replace(/\s/g,"");
39 | var num = +this.nodeName.substr(1);
40 | var section = {
41 | id: this.id,
42 | text: el.text(),
43 | num: num,
44 | sections: []
45 | };
46 |
47 | while(last() && (last().num >= num) ) {
48 | headingStack.pop();
49 | }
50 |
51 | if(!headingStack.length) {
52 | sections.push(section);
53 | headingStack.push(section);
54 | } else {
55 | last().sections.push(section);
56 | headingStack.push(section);
57 | }
58 | });
59 |
60 | // make sure to hide TOC, but still do id processing
61 | if(window.docObject.hideContents){
62 | return;
63 | }
64 |
65 | this.element.html( contentList(sections,
66 | ( ( window.docObject.outline && window.docObject.outline.tag ) || "ul" ).toLowerCase() ) );
67 |
68 | if(window.location.hash.length) {
69 | var id = window.location.hash.replace('#', ''),
70 | anchor = document.getElementById(id);
71 |
72 | if(anchor) {
73 | anchor.scrollIntoView(true);
74 | }
75 | }
76 | },
77 | collectSignatures: function() {
78 | var cloned = $('.content .signature').clone();
79 | // remove release numbers
80 | cloned.find(".release").remove();
81 | return cloned;
82 | },
83 | collectHeadings: function() {
84 | //change the depth so we capture all our headings
85 | var depth = ( window.docObject.outline && window.docObject.outline.depth ) || 3;
86 | var headings = [];
87 | for(var i = 0; i < depth; i++) {
88 | headings.push("h"+(i+2));
89 | }
90 | return $('.content .comment').find(headings.join(","));
91 | }
92 | });
93 |
94 | });
95 |
--------------------------------------------------------------------------------
/theme/static/static.js:
--------------------------------------------------------------------------------
1 | steal(
2 | // documentjs's dependencies
3 | "./content_list.js",
4 | "./frame_helper.js",
5 | "./versions.js",
6 | "./styles/styles.less!",
7 | "prettify.js",
8 |
9 | function(ContentList, FrameHelper, Versions){
10 | var codes = document.getElementsByTagName("code");
11 | for(var i = 0; i < codes.length; i ++){
12 | var code = codes[i];
13 | if(code.parentNode.nodeName.toUpperCase() === "PRE"){
14 | code.className = code.className +" prettyprint"
15 | }
16 | }
17 | prettyPrint();
18 |
19 | new ContentList(".contents");
20 | new FrameHelper(".docs");
21 | new Versions( $("#versions, .sidebar-title:first") );
22 | });
--------------------------------------------------------------------------------
/theme/templates/helpers.js:
--------------------------------------------------------------------------------
1 | module.exports = function(docMap, config, getCurrent){
2 | return {
3 | ifValidSource: function (src, options) {
4 | if (src && config.source){
5 | return options.fn(this);
6 | }else{
7 | return options.inverse(this);
8 | }
9 | },
10 | urlSource: function(src, options){
11 | var source = getCurrent().source;
12 |
13 | return source + "/tree/master/" + src;
14 | }
15 | };
16 | };
17 |
--------------------------------------------------------------------------------