Recently Will Lanier from the Out Foundation contacted me because he had found an old blog post about my Crossfit Alerts project and was trying to do something similar for the upcoming 2018 Crossfit Open but for the LGBT+ community.
He explained that he is involved in a project called OutWod to help bring together LGBT+ Crossfit athletes. They were going to have participants from this community raise funds for local charitable organizations and compete against each other during the open.
Now, the games website allows you to create custom leaderboards, but there’s no support for something like a leaderboard based on sexual orientation. Also, Will wanted to be able to support LGBT+ athletes that wanted to participate in this charity event but not formally participate in the open through the games website.
He needed some help.
Fortunately, I happened to have some time on my hands and thought it sounded like an awesome idea, so I offered to build a custom leaderboard to support the project.
Creating a Custom Leaderboard
Since I had some time and I wanted to learn some new technologies, I decided to build out a solution for this project using NodeJS and React.
I’ve been wanting to experiment with isomorphic javascript for a while, but hadn’t had the right opportunity.
I ended up building two NodeJS apps. One for collecting the data I needed automatically and one for the actual UI of the leaderboard.
Importing Participants
Back in 2013 when I created Crossfit Alerts, I had to create a crawler to index the entire games website into my own database. Luckily, the site now supports non-documented search API that responds with JSON.
You can see what this looks like here, Games Search API Endpoint, or in the partial response below.
""version":3, "dataType":"LEADERBOARD", "query":"query-string", "cacheKey":"v3.leaderboard.OPN-2018-DIV02-FIT01000-PRF00-RXD.0.alb.2536", "pagination": { "currentPage":1, "totalPages":2993, "totalCompetitors":149648 }, leaderboardRows": [ { "entrant": { "competitorId":"92567", "competitorName":"Andrey Ganin", "firstName":"Andrey", "lastName":"Ganin", ... }, "scores": [ { "ordinal":1, "rank":"1", "score":"14590000", "scoreDisplay":"459 reps", "mobileScoreDisplay":"", "scoreIdentifier":"96048ff1f8b4458918d5", "scaled":"0", "video":"0", "breakdown":"14 rounds +n8 toes-to-barsn3 clean & jerks", "judge":"Sergey Chadin", "affiliate":"CrossFit MDN", "heat":"", "lane":"" } ], "overallRank":"1", "overallScore":"1", "nextStage":"" },
This is fantastic because it makes my job significantly easier. I can automatically update people’s scores by simply querying the API endpoint and copying any new data into my database.
Every person that signs up for the Crossfit Open gets a unique athlete id assigned to them and this can be used to lookup their profile and their scores. I asked Will to collect people’s Crossfit Games athlete id’s when they signed up to participate in the competition.
I then wrote a script to parse the spreadsheet that he collected athlete information with and bring that into MySQL database. This included things like the athlete’s name, their games ID if they have one, what division they are part of, and whether they are doing RX or scaled workouts.
Automatically Collecting Participant Scores
Now that I had all the data about who was participating in the event, I needed to be able to automatically lookup their scores and save it to my database so that it could be displayed in the leaderboard automatically.
It turns out, if you search an athlete by name on the games website and then click their entry that comes up in the dropdown and inspect the HTTP request, you get something like this
https://games.crossfit.com/competitions/api/v1/competitions/open/2018/leaderboards?division=2&athlete=2536.
That returns a list of athletes but one of the athletes is guaranteed to be the athlete with ID 2536 (Sam Briggs in this case).
We can use this type of query to look up an athlete’s scores and update our local database. I simply query to retrieve all athletes and then go through each athlete and hit the API endpoint with their ID, then parse the results to find the athlete I want. The JS code is displayed below. The “gamesApi” simply packages up my object into an HTTP request and returns the JSON response.
gamesApi.performRequest('/competitions/api/v1/competitions/open/2018/leaderboards', 'GET', { athlete: athleteId, division: division, scaled: 0, sort: 0, fittest: 1, fittest1: 0, occupation: 0, page: 1 }, function(data) { for(var i = 0; i < data.leaderboardRows.length; i++) { if(data.leaderboardRows[i].entrant.competitorId == athleteId) { // update the athlete data and scores updateAthleteScores(data.leaderboardRows[i], athlete); break; } } });
I setup this NodeJS app on AWS Lambda so it can run a few times a day to import any new data.
Displaying the Leaderboard
To actually display the results of the leaderboard, I created a NodeJS project with a React front-end. I essentially mimicked the same user experience that you’d find on the Crossfit Games leaderboard.
You can click an athlete to get more details about their scores and profile information. Basic searching and filtering is also supported.
I created a free Heroku account to host the leaderboard for the duration of the open.
Supporting Custom Scores
At this point, everything seemed great, but we needed a way for non-registered athletes to be able to include their scores. On the surface, this seems simple enough, just give them a form where they can enter their scores. However, this complicates matters because up until this point I was relying on the games website’s calculation for score and rank for an athlete. By introducing custom scores that the main site has no knowledge of, it completely invalidates the scores and ranks I was using.
To solve this problem, I had to implement my own ranking and score calculations. Whenever a new score gets entered, whether picked up automatically from the games website or manually entered through a form I built, we need to re-rank all athletes.
The way I do this is I first save any new scores to my database. I then query to get all athletes and I run a custom sort function on each workout to calculate each athlete’s rank for a particular workout. The sort has to take into account RX versus scaled, reps and possibly time.
I use these rankings to calculate the athlete’s overall score and then sort the athletes by score, use this sorted order to calculate their overall rank and update the database entries.
You can see what this code looks like below:
function rankAthletes(athletes) { let workoutIds = ["18.1", "18.2", "18.3", "18.4", "18.5"]; // sort by each workout and calcualte the rank for the workout for(var i = 0; i < workoutIds.length; i++) { athletes.sort(propComparator(workoutIds[i])); // saves the rank for this athlete for the given workout updateAthleteRanks(athletes, workoutIds[i]); } // calculate athletes overall score, this is // the sum of their ranks across all workouts updateAthleteScores(athletes); // sort by score athletes.sort(scoreCompare); // update athletes overall score and rank for(var i = 0; i < athletes.length; i++) { let rank = i + 1; let score = athletes[i].dataValues.overallscore; // update database entry models.Athlete.findById(athletes[i].dataValues.id).then(athlete => { athlete.update({overallscore: score, overallrank: rank}); }); } }
The Live Result
With support for pulling data from the official games website and custom scores, the leaderboard was ready.
I sent Will some iframe code to embed the leaderboard within the OutWod Squarespace website and we were good to go.
Final Thoughts
I’m really glad that Will reached out. The project was a lot of fun and it’s for a great cause that I’m happy to play some small part in.
These types of side projects are a great way to learn new technologies and approaches to solving problems. I had a great time with my first real NodeJS and React project and I’ll definitely be relying on them more in the future.
Please contact me if you have any questions about this project.
Happy coding!
Nice article admin thanks for share your atricle keep share your knowledge i am waiting for your new post check mens winter jackets polo shirts kindly review and reply me
steel toe boots
house cleaning services
cognitive behavioral therapy
Leggings
lacrosse
baseball
ethereum
banana
It's a Very informative blog and useful article thank you for sharing with us, keep posting
React js Online Training
Angular Training
NodeJS Online Training Hyderabad
AngularJS Online Training
Hi Dear,
I Like Your Blog Very Much. I see Daily Your Blog, is A Very Useful For me.
You can also Find Leaderboard Competitions Visit your account to subscribe to Vega Prizes. Online gift card competitions with 2 per day. Play leaderboard games free. £2 entry for 30 draws per month.
Visit Now:- https://www.vegaprizes.com/my-account/