Skip to content
This repository was archived by the owner on Aug 13, 2024. It is now read-only.
Open
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

> Babyfoo project

## Environment setup
This project uses [MongoDB](https://www.mongodb.com) so don't forget to install it first.

## Build Setup

``` bash
Expand Down
22 changes: 15 additions & 7 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@ const koaBody = require('koa-bodyparser');
const cors = require('kcors');
const mongoose = require('koa-mongoose');
const json = require('koa-json');
const Nuxt = require('nuxt');
const { Builder, Nuxt } = require('nuxt');

const config = require('./nuxt.config.js');
const genericCRUD = require('./server/generic.js');

const app = new Koa();
app.use(cors());
const router = new KoaRouter();
config.dev = !(app.env === 'production');

const nuxt = new Nuxt(config);

// Build only in dev mode
if (config.dev) {
nuxt.build()
if (nuxt.options.dev) {
new Builder(nuxt)
.build()
.catch((error) => {
console.error(error); // eslint-disable-line no-console
process.exit(1);
Expand Down Expand Up @@ -48,9 +47,18 @@ router.use('/api/scores', genericCRUD('Score').routes());
app.use(router.routes());
app.use(router.allowedMethods());

app.use(async (ctx) => {
app.use((ctx) => {
ctx.status = 200; // koa defaults to 404 when it sees that status is unset
await nuxt.render(ctx.req, ctx.res);

// Solves nuxt.js issue #1206
return new Promise((resolve, reject) => {
ctx.res.on('close', resolve);
ctx.res.on('finish', resolve);
nuxt.render(ctx.req, ctx.res, (promise) => {
// nuxt.render passes a rejected promise into callback on error.
promise.then(resolve).catch(reject);
});
});
});

app.listen(3000);
3 changes: 2 additions & 1 deletion layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
<nav>
<nuxt-link to="/" class="brand"><span>Babyfoo</span></nuxt-link>
<div class="menu">
<nuxt-link to="/scores" class="pseudo button">Scores</nuxt-link>
<nuxt-link to="/users" class="pseudo button">Users</nuxt-link>
<nuxt-link to="/scores" class="pseudo button">Scores</nuxt-link>
<nuxt-link to="/statistics" class="pseudo button">Statistics</nuxt-link>
</div>
</nav>
<nuxt class="body"/>
Expand Down
5 changes: 3 additions & 2 deletions nuxt.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
dev: (process.env.NODE_ENV !== 'production'),
/*
** Headers of the page
*/
Expand All @@ -14,14 +15,14 @@ module.exports = {
** Global CSS
*/
css: [
{ src: '~assets/scss/main.scss', lang: 'scss' },
{ src: '~/assets/scss/main.scss', lang: 'scss' },
],
/*
** Customize the progress-bar color
*/
loading: { color: '#3B8070' },
plugins: [
{ src: '~plugins/toast', ssr: false },
{ src: '~/plugins/toast', ssr: false },
],
env: {
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
},
"dependencies": {
"axios": "^0.16.0",
"cross-env": "^3.1.4",
"kcors": "^1.3.2",
"cross-env": "^5.0.5",
"kcors": "^2.2.1",
"koa": "^2.0.0",
"koa-bodyparser": "^4.2.0",
"koa-json": "^2.0.2",
Expand All @@ -31,11 +31,11 @@
"vuex": "^2.3.1"
},
"devDependencies": {
"babel-eslint": "^7.1.1",
"eslint": "^3.19.0",
"eslint-config-airbnb-base": "^11.1.0",
"eslint-plugin-html": "^2.0.0",
"eslint-plugin-import": "^2.2.0",
"babel-eslint": "^8.0.1",
"eslint": "^4.8.0",
"eslint-config-airbnb-base": "^12.0.2",
"eslint-plugin-html": "^3.2.2",
"eslint-plugin-import": "^2.7.0",
"nodemon": "^1.11.0",
"scmp": "^2.0.0"
},
Expand Down
1 change: 1 addition & 0 deletions pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
<h1>Babyfoo score board</h1>
<nuxt-link to="/users" class="stack button">Users</nuxt-link>
<nuxt-link to="/scores" class="stack button">Scores</nuxt-link>
<nuxt-link to="/statistics" class="stack button">Statistics</nuxt-link>
</main>
</template>
29 changes: 18 additions & 11 deletions pages/scores/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<td class="red">{{ score.red.join(', ') }}</td>
<td class="blue">{{ score.score.blue }}</td>
<td class="red">{{ score.score.red }}</td>
<td :class="score.score.blue > score.score.red ? 'red' : 'blue'">
<td :class="score.score.blue > score.score.red ? 'blue' : 'red'">
{{ score.score.blue > score.score.red ? 'Blue' : 'Red' }}
</td>
</tr>
Expand All @@ -31,17 +31,17 @@
</table>
<form @submit.prevent="create(newScore)">
<select multiple class="blue" v-model="newScore.blue">
<option v-for="user in users" :value="user._id">
<option v-for="user in users" :value="user._id" :key="user._id">
{{ user.name }}
</option>
</select>
<select multiple class="red" v-model="newScore.red">
<option v-for="user in users" :value="user._id">
<option v-for="user in users" :value="user._id" :key="user._id">
{{ user.name }}
</option>
</select>
<input type="number" max="10" min="0" class="stack blue" v-model.number="newScore.score.blue" placeholder="Blue Score" />
<input type="number" max="10" min="0" class="stack red" v-model.number="newScore.score.red" placeholder="Red Score" />
<input required type="number" max="10" min="0" class="stack blue" v-model.number="newScore.score.blue" placeholder="Blue Score" />
<input required type="number" max="10" min="0" class="stack red" v-model.number="newScore.score.red" placeholder="Red Score" />
<button class="stack" type="submit">Envoyer</button>
</form>
</div>
Expand All @@ -56,9 +56,9 @@
blue: [],
red: [],
score: {
red: null,
blue: null,
}
red: undefined,
blue: undefined,
},
};
}

Expand All @@ -77,10 +77,17 @@
}),
},
methods: {
areSameArrays(a, b) {
return !a.some((value, index) => value !== b[index])
},
async create(score) {
if (this.areSameArrays(score.red, score.blue)) {
this.$toasted.error("You can't play against yourself, duuh!");
return;
}
try {
await this.scoreCreate(score);
this.$toasted.success('Score saved !');
this.$toasted.success('Score saved!');
this.newScore = generateFreshScore();
} catch (e) {
this.$toasted.error(e.response.data);
Expand All @@ -90,8 +97,8 @@
scoreCreate: 'score/create',
}),
flushNewScore() {
this.newScore = generateFreshScore();
this.newScore = generateFreshScore();
},
},
};
</script>
</script>
90 changes: 90 additions & 0 deletions pages/statistics/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<template>
<main>
<h1>Statistics</h1>
<div class="flex two">
<table>
<thead>
<tr>
<th>Team</th>
<th>Total score</th>
<th>Games won / Games played</th>
</tr>
</thead>
<tbody>
<tr v-for="statistic in statistics" v-bind:key="statistic.team">
<td>{{ statistic.team }}</td>
<td>{{ statistic.score }}</td>
<td>{{ Math.round(statistic.statistics) }}%</td>
</tr>
</tbody>
</table>
</div>
</main>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
async asyncData({ store }) {
await store.dispatch('score/fetchAll');
},
methods: {
sort_by_statistics(a, b) {
if (a.statistics < b.statistics) {
return 1;
}
if (a.statistics > b.statistics) {
return -1;
}
return 0;
},
filterTeams() {
const teams = [];
for (const score of this.scores) {
if (score.score.blue || score.score.red) {
const teamRed = { team: score.red.join(','), score: score.score.red, playedTimes: 1, winner: 0 };
const teamBlue = { team: score.blue.join(','), score: score.score.blue, playedTimes: 1, winner: 0 };
if (teamRed.score > teamBlue.score) {
teamRed.winner = 1;
} else {
teamBlue.winner = 1;
}
teams.push(teamRed, teamBlue);
}
}
const filteredTeams = [];
for (const team of teams) {
const index = filteredTeams.findIndex(element => element.team === team.team);
if (index < 0) {
filteredTeams.push(team);
} else {
filteredTeams[index].score += team.score;
filteredTeams[index].winner += team.winner;
filteredTeams[index].playedTimes += team.playedTimes;
}
}
for (const team of filteredTeams) {
team.statistics = 100 * (team.winner / team.playedTimes);
}
return filteredTeams;
},
},
computed: {
statistics() {
const totalGames = this.scores.length;
return this.filterTeams().sort(this.sort_by_statistics);
},
...mapGetters({
scores: 'score/scores',
}),
},
};
</script>

<style scoped>
th,
td {
text-align: center;
}
</style>
13 changes: 7 additions & 6 deletions pages/users/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
</tr>
</thead>
<tbody>
<tr v-for="user in users">
<tr v-for="user in users" v-bind:key="user._id">
<td>{{ user.name }}</td>
<td><button @click="changeCurrentUser(user)">Edit</button></td>
</tr>
</tbody>
<tfoot>
<tr>
<th colspan="2"><button @click="changeCurrentUser(null)">New</button></th>
<th colspan="2"><button @click="changeCurrentUser(undefined)">New</button></th>
</tr>
</tfoot>
</table>
Expand All @@ -36,7 +36,7 @@
function generateFreshUserToEdit() {
return {
name: '',
_id: null,
_id: undefined,
};
}
export default {
Expand Down Expand Up @@ -65,11 +65,12 @@
...mapActions({
userSave: 'user/save',
}),
changeCurrentUser(user = null) {
if (!user) {
changeCurrentUser(user = undefined) {
if (user === undefined) {
this.userToEdit = generateFreshUserToEdit();
} else {
this.userToEdit = this.getUser(user._id);
const real_user = this.getUser(user._id);
this.userToEdit = { ...real_user };
}
},
},
Expand Down
6 changes: 6 additions & 0 deletions plugins/api/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,11 @@ export default Object.assign(
fetchAll() {
return this.get('/users');
},
fetchOne(id) {
return this.get(`/users/${id}`);
},
update(user) {
return this.put('/users', user);
},
},
);
13 changes: 13 additions & 0 deletions server/generic.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,18 @@ module.exports = function generic(modelName) {
const User = model(modelName);
ctx.body = await User.find();
});
router.get('/:id', async (ctx) => {
const { model, params } = ctx;
const User = model(modelName);
const user = await User.findById(params.id);
ctx.body = user;
});
router.put('/', async (ctx) => {
const { model, request } = ctx;
const User = model(modelName);
const user = request.body;
await User.update({ _id: user._id }, user);
ctx.body = { success: 'ok' };
});
return router;
};
2 changes: 1 addition & 1 deletion store/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const state = {};
export const state = () => {};

export const getters = {};

Expand Down
6 changes: 3 additions & 3 deletions store/score.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import ScoreAPI from '~plugins/api/score';
import ScoreAPI from '~/plugins/api/score';

export const state = {
export const state = () => ({
scores: [],
};
});

export const getters = {
scores: state => state.scores,
Expand Down
Loading