Skip to content

Commit

Permalink
Fixed #39 and added migrations, changed estimate format to hh:mm:ss
Browse files Browse the repository at this point in the history
  • Loading branch information
CBenni committed Apr 1, 2019
1 parent 7f94631 commit c820db5
Show file tree
Hide file tree
Showing 16 changed files with 166 additions and 103 deletions.
32 changes: 20 additions & 12 deletions backend/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import settings from './settings';
import { models } from './models';
import { twitchGet } from './twitchAPI';
import { generateToken, decodeToken } from './auth';
import { notify, httpPost, httpReq } from './helpers';
import { sendDiscordSubmission, sendDiscordSubmissionUpdate, sendDiscordSubmissionDeletion } from './discordWebhooks';
import {
notify, httpPost, httpReq, teamsToString
} from './helpers';
import {
sendDiscordSubmission, sendDiscordSubmissionUpdate, sendDiscordSubmissionDeletion
} from './discordWebhooks';


const historySep = settings.vue.mode === 'history' ? '' : '#/';
Expand Down Expand Up @@ -268,7 +272,7 @@ export async function getFeedForEvent(req, res) {
export async function getUserSubmissions(req, res) {
if (!req.jwt) return res.status(401).end('Not authenticated.');
console.log('Getting user submissions with ID ', req.jwt.user.id);
return res.json(await models.Submission.find({ user: req.jwt.user.id }, 'event user game twitchGame leaderboards category platform estimate runType teams video comment status notes invitations incentives')
return res.json(await models.Submission.find({ user: req.jwt.user.id }, 'event user game twitchGame leaderboards category platform estimate runType teams runners video comment status notes invitations incentives')
.populate({ path: 'invitations', populate: { path: 'user', select: 'connections.twitch.displayName connections.twitch.id connections.twitch.logo' } })
.populate({ path: 'teams.members', populate: { path: 'user', select: 'connections.twitch.displayName connections.twitch.id connections.twitch.logo' } }));
}
Expand Down Expand Up @@ -378,24 +382,30 @@ export async function updateUserApplication(req, res) {
export async function updateUserSubmission(req, res) {
// TODO: verify event
if (!req.jwt) return res.status(401).end('Not authenticated.');
let submission = await models.Submission.findById(req.body._id).exec();
let submission = await models.Submission.findById(req.body._id)
.populate({ path: 'user', select: 'connections.twitch.displayName connections.twitch.name' })
.populate({ path: 'teams.members', populate: { path: 'user', select: 'connections.twitch.displayName connections.twitch.name' } })
.exec();
// console.log(`Found existing submission for ${req.body._id}:`, submission);
const validChanges = _.pick(req.body, ['game', 'twitchGame', 'leaderboards', 'category', 'platform', 'estimate', 'runType', 'teams', 'video', 'comment', 'invitations', 'incentives']);
if (['stub', 'saved', 'deleted'].includes(req.body.status)) validChanges.status = req.body.status;

if (!req.body.event) return res.status(400).end('Invalid event ID');
let changeType;
const oldVals = {};
let user;
const user = await models.User.findById(req.jwt.user.id, 'roles connections.twitch.name connections.twitch.displayName').populate('roles.role').exec();
if (submission) {
user = await models.User.findById(req.jwt.user.id, 'roles connections.twitch.name connections.twitch.displayName').populate('roles.role').exec();
// users can only edit their own runs (unless they have the Edit Runs permission)
if (!submission.user.equals(req.jwt.user.id) && !hasPermission(user, req.body.event, 'Edit Runs')) return res.status(403).end(`Access denied to user ${req.jwt.user.id}`);
// validate invites and teams
if ((validChanges.teams && validChanges.teams.length > 0) || (validChanges.invitations && validChanges.invitations.length > 0)) {
const allInvites = new Set(_.map(await models.Invitation.find({ submission: req.body._id }), invite => invite && invite._id.toString()));
const allMembers = _.map(_.flattenDeep([validChanges.invitations || [], _.map(validChanges.teams, team => team.members) || []]), member => member._id || member);

// update the runners
const runType = validChanges.runType || submission.runType;
validChanges.runners = runType === 'solo' ? submission.user.connections.twitch.displayName : teamsToString(validChanges.teams || submission.teams);

// make sure no invites got added or removed
for (let i = 0; i < allMembers.length; ++i) {
const member = allMembers[i];
Expand Down Expand Up @@ -434,6 +444,7 @@ export async function updateUserSubmission(req, res) {
user: req.jwt.user.id,
event: req.body.event,
status: req.body.status || 'stub',
runners: user.connections.twitch.displayName,
...validChanges
});
if (submission.status === 'saved') changeType = 'new';
Expand Down Expand Up @@ -696,16 +707,13 @@ export async function getSubmissions(req, res) {
let runs = [];
if (hasPermission(user, req.query.event, runDecisionPermission)) {
console.log('Has permission');
runs = await models.Submission.find({ event: req.query.event, status: { $in: ['saved', 'accepted', 'declined'] } })
runs = await models.Submission.find({ event: req.query.event, status: { $in: ['saved', 'accepted', 'declined'] } }, 'event user game category platform estimate runType runners video comment decisions')
.populate('user', 'connections.twitch.name connections.twitch.displayName connections.twitch.logo connections.srdotcom.name')
.populate({ path: 'teams.members', populate: { path: 'user', select: 'connections.twitch.displayName connections.twitch.id connections.twitch.logo' } })
.populate({ path: 'invitations', populate: { path: 'user', select: 'connections.twitch.displayName connections.twitch.id connections.twitch.logo' } })
.exec();
} else {
console.log('Doesnt have permission');
runs = await models.Submission.find({ event: req.query.event, status: { $in: ['saved', 'accepted', 'declined'] } }, 'event user game leaderboards category platform estimate runType teams')
runs = await models.Submission.find({ event: req.query.event, status: { $in: ['saved', 'accepted', 'declined'] } }, 'event user game category platform estimate runType runners')
.populate('user', 'connections.twitch.name connections.twitch.displayName connections.twitch.logo connections.srdotcom.name')
.populate({ path: 'teams.members', populate: { path: 'user', select: 'connections.twitch.displayName' } })
.exec();
}
return res.json(runs);
Expand All @@ -714,7 +722,7 @@ export async function getSubmissions(req, res) {
export async function getSubmission(req, res) {
if (!req.jwt) return res.status(401).end('Not authenticated.');
if (!req.params.id) return res.status(400).end('Missing query parameter id');
return res.json(await models.Submission.findById(req.params.id, 'event user game twitchGame leaderboards category platform estimate runType teams invitations video comment status incentives')
return res.json(await models.Submission.findById(req.params.id, 'event user game twitchGame leaderboards category platform estimate runType teams runners invitations video comment status incentives')
.populate('user', 'connections.twitch.name connections.twitch.displayName connections.twitch.logo connections.srdotcom.name')
.populate({ path: 'teams.members', populate: { path: 'user', select: 'connections.twitch.displayName connections.twitch.id connections.twitch.logo' } })
.populate({ path: 'invitations', populate: { path: 'user', select: 'connections.twitch.displayName connections.twitch.id connections.twitch.logo' } })
Expand Down
12 changes: 10 additions & 2 deletions backend/src/backend.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
import './firsttimesetup';
import './routes';
import db from './db';
import migrate from './migrate';
import logger from './logger';

db.then(async () => {
logger.info('Running migrations...');
await migrate();
logger.info('Migrations done, starting server...');
require('./routes'); // eslint-disable-line global-require
});
33 changes: 5 additions & 28 deletions backend/src/discordWebhooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import _ from 'lodash';
import settings from './settings';
import logger from './logger';

export function teamsToString(teams) {
return _.map(teams, team => _.map(team.members, member => member.user && member.user.connections.twitch.displayName).join(', ')).join(' vs ');
}

async function publicWebhook(data) {
try {
logger.debug('Sending public webhook', data);
Expand Down Expand Up @@ -47,32 +43,30 @@ export function sendDiscordSubmission(user, submission) {
const submitterTwitchName = submitterTwitchUser.displayName.toLowerCase() === submitterTwitchUser.name ? submitterTwitchUser.displayName : `${submitterTwitchUser.displayName} (${submitterTwitchUser.name})`;
const discordUser = submission.user.connections.discord;
let category = submission.category;
let teams = '';
if (submission.runType !== 'solo') {
category += ` (${submission.runType})`;
teams = teamsToString(submission.teams);
}
let { incentive: incentives, bidwar: bidwars } = _.groupBy(submission.incentives, 'type');
incentives = _.map(incentives, 'name').join(', ');
bidwars = _.map(bidwars, 'name').join(', ');
publicWebhook({
title: 'A new run has been submitted!',
url: `${settings.frontend.baseurl}${settings.vue.mode === 'history' ? '' : '#/'}dashboard/submissions/${submission._id}`,
description: `${submitterTwitchName} has just submited a new run!\n\n`
description: `${submitterTwitchName} has just submitted a new run!\n\n`
+ `**Game:** ${submission.game}\n`
+ `**Category:** ${category}\n`
+ `**Platform:** ${submission.platform}`
});
privateWebhook({
title: 'A new run has been submitted!',
url: `${settings.frontend.baseurl}${settings.vue.mode === 'history' ? '' : '#/'}dashboard/submissions/${submission._id}`,
description: `${submitterTwitchName} has just submited a new run!\n\n` // eslint-disable-line prefer-template
description: `${submitterTwitchName} has just submitted a new run!\n\n` // eslint-disable-line prefer-template
+ (discordUser ? `**Discord user:** <@${discordUser.id}> (${discordUser.name}#${discordUser.discriminator})\n` : '')
+ `**Game:** ${submission.game}\n`
+ `**Category:** ${category}\n`
+ `**Platform:** ${submission.platform}\n`
+ `**Estimate:** ${submission.estimate}\n`
+ (teams ? `**Players:** ${teams}\n` : '')
+ `**Runners:** ${submission.runners}\n`
+ `**Video:** ${submission.video}\n`
+ (incentives ? `**Incentives:** ${incentives}\n` : '')
+ (bidwars ? `**Bid wars:** ${bidwars}` : '')
Expand All @@ -86,25 +80,10 @@ export function sendDiscordSubmissionUpdate(user, submission, changes) {
const userTwitchName = userTwitchUser.displayName.toLowerCase() === userTwitchUser.name ? userTwitchUser.displayName : `${userTwitchUser.displayName} (${userTwitchUser.name})`;
const discordUser = submission.user.connections.discord;
let category = submission.category;
let teams = '';
if (submission.runType !== 'solo') {
category += ` (${submission.runType})`;
teams = teamsToString(submission.teams);
}

let teamsChanged = false;
_.each(submission.teams, (team, i) => {
if (!changes.teams[i]) {
teamsChanged = true;
return;
}
_.each(team.members, (member, j) => {
if (!member._id.equals(changes.teams[i].members[j])) {
teamsChanged = true;
}
});
});

let oldCategory = `${changes.category || submission.category}`;
if ((changes.runType && changes.runType !== 'solo') || submission.runType !== 'solo') {
oldCategory += ` (${changes.runType || submission.runType})`;
Expand All @@ -124,7 +103,7 @@ export function sendDiscordSubmissionUpdate(user, submission, changes) {
category !== oldCategory && `**Category:** ${category} (was: ${oldCategory})`,
changes.platform && `**Platform:** ${submission.platform} (was: ${changes.platform})`,
changes.estimate && `**Estimate:** ${submission.estimate} (was: ${changes.estimate})`,
teamsChanged && `**Players:** ${teams}`,
changes.runners && `**Runners:** ${submission.runners} (was: ${changes.runners})`,
changes.video && `**Video:** ${submission.video} (was: ${changes.video})`,
incentives !== oldIncentives && `**Incentives:** ${incentives} (were: ${oldIncentives})`,
bidwars !== oldBidwars && `**Bid wars:** ${bidwars} (were: ${oldBidwars})`
Expand All @@ -150,10 +129,8 @@ export function sendDiscordSubmissionDeletion(user, submission, changeType) {
const userTwitchName = userTwitchUser.displayName.toLowerCase() === userTwitchUser.name ? userTwitchUser.displayName : `${userTwitchUser.displayName} (${userTwitchUser.name})`;
const discordUser = submission.user.connections.discord;
let category = submission.category;
let teams = '';
if (submission.runType !== 'solo') {
category += ` (${submission.runType})`;
teams = teamsToString(submission.teams);
}
let { incentive: incentives, bidwar: bidwars } = _.groupBy(submission.incentives, 'type');
incentives = _.map(incentives, 'name').join(', ');
Expand All @@ -168,7 +145,7 @@ export function sendDiscordSubmissionDeletion(user, submission, changeType) {
+ `**Category:** ${category}\n`
+ `**Platform:** ${submission.platform}\n`
+ `**Estimate:** ${submission.estimate}\n`
+ (teams ? `**Players:** ${teams}\n` : '')
+ `**Runners:** ${submission.runners}\n`
+ `**Video:** ${submission.video}\n`
+ (incentives ? `**Incentives:** ${incentives}\n` : '')
+ (bidwars ? `**Bid wars:** ${bidwars}` : '')
Expand Down
36 changes: 0 additions & 36 deletions backend/src/firsttimesetup.js

This file was deleted.

4 changes: 4 additions & 0 deletions backend/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ export function httpPost(url, params) {
console.log('HTTP POST params:', p, p.body.toString());
return httpReq(url, p);
}

export function teamsToString(teams) {
return _.map(teams, team => _.map(team.members, member => member.user && member.user.connections.twitch.displayName).join(', ')).join(' vs ');
}
27 changes: 27 additions & 0 deletions backend/src/migrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import _ from 'lodash';
import { models } from './models';
import migrations from './migrations';
import logger from './logger';

export default async function migrate() {
const migrationsRun = await models.Migration.find({});

for (let i = 0; i < migrations.length; ++i) {
const migration = migrations[i];
if (_.find(migrationsRun, { id: migration.id })) {
logger.debug(`Skipping migration ${migration.id}`);
} else {
logger.info(`Running migration ${migration.id}`);
logger.info(migration.description);
const res = await migration.run(); // eslint-disable-line no-await-in-loop
const migrationResult = new models.Migration({
id: migration.id,
description: migration.description,
result: res
});
logger.info('Migration result:', res);
await migrationResult.save(); // eslint-disable-line no-await-in-loop
logger.info(`Migration ${migration.id} done`);
}
}
}
72 changes: 72 additions & 0 deletions backend/src/migrations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import _ from 'lodash';
import { models } from './models';
import { teamsToString } from './helpers';
import settings from './settings';

export default [
{
id: 'setupEvents',
description: 'Sets up the events for the first time',
async run() {
// set up default event if not defined
const events = await models.Event.find();
if (events.length === 0) {
const defaultEvent = new models.Event({
name: 'Default Event'
});
return defaultEvent.save();
}
return null;
}
},
{
id: 'setupRoles',
description: 'Sets up the roles for the first time',
async run() {
// set up default roles if not defined
const roles = await models.Role.find();
if (roles.length === 0) {
return Promise.all(_.map(settings.defaultRoles, role => {
const defaultRole = new models.Role(role);
return defaultRole.save();
}));
}
return null;
}
},
{
id: 'fixTimes',
description: 'Updates estimates from hh:mm format (which turned out to not be very user friendly) to hh:mm:ss format "intelligently"',
async run() {
const allRuns = await models.Submission.find({}, 'estimate');
await Promise.all(_.map(allRuns, run => {
const match = /^(\d+):(\d+)$/.exec(run.estimate);
if (match) {
const [, hrs, mins] = match;
if (parseInt(hrs, 10) < 15) {
run.estimate = `${hrs.padStart(2, '0')}:${mins}:00`;
} else {
run.estimate = `00:${hrs.padStart(2, '0')}:${mins}`;
}
return run.save();
}
return true;
}));
return `Updated the estimates on ${allRuns.length} runs`;
}
},
{
id: 'addRunners',
description: 'Adds the runners property to all runs',
async run() {
const allRuns = await models.Submission.find({}, 'user runType teams')
.populate('user', 'connections.twitch.displayName')
.populate({ path: 'teams.members', populate: { path: 'user', select: 'connections.twitch.displayName' } });
await Promise.all(_.map(allRuns, run => {
run.runners = run.runType === 'solo' ? run.user.connections.twitch.displayName : teamsToString(run.teams);
return run.save();
}));
return `Updated the runners on ${allRuns.length} runs`;
}
}
];
10 changes: 10 additions & 0 deletions backend/src/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const Submission = new mongoose.Schema({
estimate: String,
runType: String, // 'solo', race', 'coop', 'relay'
teams: [Team],
runners: String, // this is a cached version of the team or just the runner display name in case of solo runs
video: String,
comment: String,
status: String,
Expand Down Expand Up @@ -207,10 +208,19 @@ const FeedItem = new mongoose.Schema({
});
FeedItem.index({ event: 1 });

const Migration = new mongoose.Schema({
id: String,
description: String,
result: Object
}, {
timestamps: true
});

export const schemas = {
User, Role, Submission, Event, TwitchConnection, DiscordConnection, SrDotComConnection, Invitation
};
export const models = {
Migration: mongoose.model('migration', Migration),
Event: mongoose.model('event', Event),
User: mongoose.model('user', User),
Role: mongoose.model('role', Role),
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/dashboard/PublicSubmissions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</div>
<div class="flex info-items layout-row">
<div class="infinite-td flex-30 name">{{item.game}} ({{item.category}}{{item.runType === 'solo' ? '' : ' '+item.runType}})</div>
<div class="infinite-td flex-30 runners"><span class="mobile-description">Runners: </span>{{getRunners(item)}}</div>
<div class="infinite-td flex-30 runners"><span class="mobile-description">Runners: </span>{{item.runners}}</div>
<div class="infinite-td flex-20 platform"><span class="mobile-description">Platform: </span>{{item.platform}}</div>
<div class="infinite-td flex-10 estimate"><span class="mobile-description">Estimate: </span>{{item.estimate}}</div>
</div>
Expand Down
Loading

0 comments on commit c820db5

Please sign in to comment.